RabbitMQ Auth Context tracking requirements

This commit is contained in:
its-a-feature
2026-05-21 12:33:44 -07:00
parent 70e28b9de5
commit 7d6249f153
245 changed files with 4231 additions and 2807 deletions
+100 -79
View File
@@ -1,29 +1,12 @@
import { LoginForm } from './pages/Login/LoginForm';
import { Settings } from './pages/Settings/Settings';
import { PayloadTypesC2Profiles } from './pages/PayloadTypesC2Profiles/PayloadTypesC2Profiles';
import { CreatePayload } from './pages/CreatePayload/CreatePayload';
import { CreatePayloadWrapper } from './pages/CreateWrapper/CreatePayload';
import { EventFeed } from './pages/EventFeed/EventFeed';
import { Operations } from './pages/Operations/Operations';
import { BrowserScripts } from './pages/BrowserScripts/BrowserScripts';
import { Payloads } from './pages/Payloads/Payloads';
import { ExpandedCallback } from './pages/ExpandedCallback/ExpandedCallback';
import { Home } from './pages/Home/Home';
import { LoggedInRoute } from './utilities/LoggedInRoute';
import { Callbacks } from './pages/Callbacks/Callbacks';
import { Search } from './pages/Search/Search';
import React, {createContext} from 'react';
import { Typography } from '@mui/material';
import { useReactiveVar } from '@apollo/client';
import { useDarkMode } from './utilities/useDarkMode';
import { SingleTaskView } from './pages/SingleTaskView/SingleTaskView';
import { createTheme, ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
import { GlobalStyles } from '../themes/GlobalStyles';
import CssBaseline from '@mui/material/CssBaseline';
import {FailedRefresh, mePreferences, meState, operatorSettingDefaults} from '../cache';
import { Reporting } from './pages/Reporting/Reporting';
import { MitreAttack } from './pages/MITRE_ATTACK/MitreAttack';
import {Tags} from './pages/Tags/Tags';
import { Tooltip } from 'react-tooltip';
import {useLazyQuery, gql } from '@apollo/client';
//background-color: #282c34;
@@ -34,14 +17,37 @@ import { RefreshTokenDialog } from './RefreshTokenDialog';
import { MythicDialog } from './MythicComponents/MythicDialog';
import { ToastContainer } from 'react-toastify';
import "react-toastify/dist/ReactToastify.css";
import {Eventing} from "./pages/Eventing/Eventing";
import {InviteForm} from "./pages/Login/InviteForm";
import {snackActions} from "./utilities/Snackbar";
import {TopAppBarVertical} from "./TopAppBarVertical";
import {MythicLoadingState} from "./MythicComponents/MythicStateDisplay";
import { library } from '@fortawesome/fontawesome-svg-core';
import * as Icons from '@fortawesome/free-solid-svg-icons';
import {Jupyter} from "./pages/Jupyter/Jupyter";
import {Hasura} from "./pages/Hasura/Hasura";
const lazyNamed = (importer, exportName) => React.lazy(() =>
importer().then((module) => ({default: module[exportName]}))
);
const LoginForm = lazyNamed(() => import('./pages/Login/LoginForm'), 'LoginForm');
const InviteForm = lazyNamed(() => import('./pages/Login/InviteForm'), 'InviteForm');
const Settings = lazyNamed(() => import('./pages/Settings/Settings'), 'Settings');
const PayloadTypesC2Profiles = lazyNamed(() => import('./pages/PayloadTypesC2Profiles/PayloadTypesC2Profiles'), 'PayloadTypesC2Profiles');
const CreatePayload = lazyNamed(() => import('./pages/CreatePayload/CreatePayload'), 'CreatePayload');
const CreatePayloadWrapper = lazyNamed(() => import('./pages/CreateWrapper/CreatePayload'), 'CreatePayloadWrapper');
const EventFeed = lazyNamed(() => import('./pages/EventFeed/EventFeed'), 'EventFeed');
const Operations = lazyNamed(() => import('./pages/Operations/Operations'), 'Operations');
const BrowserScripts = lazyNamed(() => import('./pages/BrowserScripts/BrowserScripts'), 'BrowserScripts');
const Payloads = lazyNamed(() => import('./pages/Payloads/Payloads'), 'Payloads');
const ExpandedCallback = lazyNamed(() => import('./pages/ExpandedCallback/ExpandedCallback'), 'ExpandedCallback');
const Home = lazyNamed(() => import('./pages/Home/Home'), 'Home');
const Callbacks = lazyNamed(() => import('./pages/Callbacks/Callbacks'), 'Callbacks');
const Search = lazyNamed(() => import('./pages/Search/Search'), 'Search');
const SingleTaskView = lazyNamed(() => import('./pages/SingleTaskView/SingleTaskView'), 'SingleTaskView');
const Reporting = lazyNamed(() => import('./pages/Reporting/Reporting'), 'Reporting');
const MitreAttack = lazyNamed(() => import('./pages/MITRE_ATTACK/MitreAttack'), 'MitreAttack');
const Tags = lazyNamed(() => import('./pages/Tags/Tags'), 'Tags');
const Eventing = lazyNamed(() => import('./pages/Eventing/Eventing'), 'Eventing');
const Jupyter = lazyNamed(() => import('./pages/Jupyter/Jupyter'), 'Jupyter');
const Hasura = lazyNamed(() => import('./pages/Hasura/Hasura'), 'Hasura');
// add all fas icons
const iconList = Object
@@ -757,7 +763,7 @@ export function App(props) {
...getModernThemeAdditions(themeMode, preferences),
})
}catch(error){
console.log(error);
console.log("error creating theme", error);
snackActions.error(error.message);
return createTheme({
transitions: {
@@ -852,6 +858,20 @@ export function App(props) {
operatorSettingDefaults.palette.navBarColor.light,
topAppBarBottomColor: themeMode === 'dark' ? operatorSettingDefaults.palette.navBarBottomColor.dark :
operatorSettingDefaults.palette.navBarBottomColor.light,
typography: {
fontSize: 12,//operatorSettingDefaults?.fontSize,
fontFamily: operatorSettingDefaults?.fontFamily
},
taskPromptTextColor: themeMode === 'dark' ? operatorSettingDefaults.palette.taskPromptTextColor.dark :
operatorSettingDefaults.palette.taskPromptTextColor.light,
taskPromptCommandTextColor: themeMode === 'dark' ? operatorSettingDefaults.palette.taskPromptCommandTextColor.dark :
operatorSettingDefaults.palette.taskPromptCommandTextColor.light,
taskContextColor: themeMode === 'dark' ? operatorSettingDefaults.palette.taskContextColor.dark :
operatorSettingDefaults.palette.taskContextColor.light,
taskContextImpersonationColor: themeMode === 'dark' ? operatorSettingDefaults.palette.taskContextImpersonationColor.dark :
operatorSettingDefaults.palette.taskContextImpersonationColor.light,
taskContextExtraColor: themeMode === 'dark' ? operatorSettingDefaults.palette.taskContextExtraColor.dark :
operatorSettingDefaults.palette.taskContextExtraColor.light,
emptyFolderColor: themeMode === 'dark' ? operatorSettingDefaults.palette.emptyFolderColor.dark :
operatorSettingDefaults.palette.emptyFolderColor.light,
outputBackgroundColor: themeMode === 'dark' ? operatorSettingDefaults.palette.outputBackgroundColor.dark :
@@ -860,10 +880,6 @@ export function App(props) {
operatorSettingDefaults.palette.outputTextColor.light,
borderColor: themeMode === 'dark' ? operatorSettingDefaults.palette.borderColor.dark :
operatorSettingDefaults.palette.borderColor.light,
typography: {
fontSize: 12,//operatorSettingDefaults?.fontSize,
fontFamily: operatorSettingDefaults?.fontFamily
},
...getModernThemeAdditions(themeMode, operatorSettingDefaults),
})
}
@@ -918,6 +934,9 @@ export function App(props) {
<ThemeProvider theme={theme}>
<GlobalStyles theme={theme} />
<CssBaseline />
<div style={{width: '100%', height: '100%', display: "flex", position: "relative",}}>
<MythicLoadingState compact title="Loading Preferences" description="Fetching user preferences." sx={{color: "inherit"}} />
</div>
</ThemeProvider>
</StyledEngineProvider>
)
@@ -1001,59 +1020,61 @@ export function App(props) {
flexDirection: 'column',
overflow:"hidden"
}}>
<Routes>
<Route path='/new/login' element={<LoginForm me={me}/>}/>
<Route path='/new/invite' element={<InviteForm me={me}/>}/>
<Route path='/' element={<LoggedInRoute me={me}><Home me={me}/></LoggedInRoute>}/>
<Route exact path='/new'
element={<LoggedInRoute me={me}><Home me={me}/></LoggedInRoute>}/>
<Route exact path='/new/settings'
element={<LoggedInRoute me={me}><Settings me={me}/></LoggedInRoute>}/>
<Route exact path='/new/payloadtypes'
element={<LoggedInRoute me={me}><PayloadTypesC2Profiles
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/eventfeed'
element={<LoggedInRoute me={me}><EventFeed me={me}/></LoggedInRoute>}/>
<Route exact path='/new/createpayload'
element={<LoggedInRoute me={me}><CreatePayload me={me}/></LoggedInRoute>}/>
<Route exact path='/new/createwrapper'
element={<LoggedInRoute me={me}><CreatePayloadWrapper
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/payloads'
element={<LoggedInRoute me={me}><Payloads me={me}/></LoggedInRoute>}/>
<Route exact path='/new/c2profiles'
element={<LoggedInRoute me={me}><PayloadTypesC2Profiles
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/services/'
element={<LoggedInRoute me={me}><PayloadTypesC2Profiles
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/callbacks'
element={<LoggedInRoute me={me}><Callbacks me={me}/></LoggedInRoute>}/>
<Route path='/new/search'
element={<LoggedInRoute me={me}><Search history={props.history}
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/browserscripts'
element={<LoggedInRoute me={me}><BrowserScripts me={me}/></LoggedInRoute>}/>
<Route exact path='/new/task/:taskId'
element={<LoggedInRoute me={me}><SingleTaskView me={me}/></LoggedInRoute>}/>
<Route exact path='/new/tasks/by_range'
element={<LoggedInRoute me={me}><SingleTaskView me={me}/></LoggedInRoute>}/>
<Route exact path='/new/operations'
element={<LoggedInRoute me={me}><Operations me={me}/></LoggedInRoute>}/>
<Route exact path='/new/callbacks/:callbackDisplayId'
element={<LoggedInRoute me={me}><ExpandedCallback
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/reporting'
element={<LoggedInRoute me={me}><Reporting me={me}/></LoggedInRoute>}/>
<Route exact path='/new/mitre'
element={<LoggedInRoute me={me}><MitreAttack me={me}/></LoggedInRoute>}/>
<Route exact path='/new/tagtypes'
element={<LoggedInRoute me={me}><Tags me={me}/></LoggedInRoute>}/>
<Route exact path='/new/eventing'
element={<LoggedInRoute me={me}><Eventing me={me}/></LoggedInRoute>}/>
<Route exact path='/new/jupyter' element={<LoggedInRoute me={me}><Jupyter/></LoggedInRoute>}/>
<Route exact path='/new/hasura' element={<LoggedInRoute me={me}><Hasura/></LoggedInRoute>}/>
</Routes>
<React.Suspense fallback={<div style={{height: "100%", width: "100%"}} />}>
<Routes>
<Route path='/new/login' element={<LoginForm me={me}/>}/>
<Route path='/new/invite' element={<InviteForm me={me}/>}/>
<Route path='/' element={<LoggedInRoute me={me}><Home me={me}/></LoggedInRoute>}/>
<Route exact path='/new'
element={<LoggedInRoute me={me}><Home me={me}/></LoggedInRoute>}/>
<Route exact path='/new/settings'
element={<LoggedInRoute me={me}><Settings me={me}/></LoggedInRoute>}/>
<Route exact path='/new/payloadtypes'
element={<LoggedInRoute me={me}><PayloadTypesC2Profiles
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/eventfeed'
element={<LoggedInRoute me={me}><EventFeed me={me}/></LoggedInRoute>}/>
<Route exact path='/new/createpayload'
element={<LoggedInRoute me={me}><CreatePayload me={me}/></LoggedInRoute>}/>
<Route exact path='/new/createwrapper'
element={<LoggedInRoute me={me}><CreatePayloadWrapper
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/payloads'
element={<LoggedInRoute me={me}><Payloads me={me}/></LoggedInRoute>}/>
<Route exact path='/new/c2profiles'
element={<LoggedInRoute me={me}><PayloadTypesC2Profiles
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/services/'
element={<LoggedInRoute me={me}><PayloadTypesC2Profiles
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/callbacks'
element={<LoggedInRoute me={me}><Callbacks me={me}/></LoggedInRoute>}/>
<Route path='/new/search'
element={<LoggedInRoute me={me}><Search history={props.history}
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/browserscripts'
element={<LoggedInRoute me={me}><BrowserScripts me={me}/></LoggedInRoute>}/>
<Route exact path='/new/task/:taskId'
element={<LoggedInRoute me={me}><SingleTaskView me={me}/></LoggedInRoute>}/>
<Route exact path='/new/tasks/by_range'
element={<LoggedInRoute me={me}><SingleTaskView me={me}/></LoggedInRoute>}/>
<Route exact path='/new/operations'
element={<LoggedInRoute me={me}><Operations me={me}/></LoggedInRoute>}/>
<Route exact path='/new/callbacks/:callbackDisplayId'
element={<LoggedInRoute me={me}><ExpandedCallback
me={me}/></LoggedInRoute>}/>
<Route exact path='/new/reporting'
element={<LoggedInRoute me={me}><Reporting me={me}/></LoggedInRoute>}/>
<Route exact path='/new/mitre'
element={<LoggedInRoute me={me}><MitreAttack me={me}/></LoggedInRoute>}/>
<Route exact path='/new/tagtypes'
element={<LoggedInRoute me={me}><Tags me={me}/></LoggedInRoute>}/>
<Route exact path='/new/eventing'
element={<LoggedInRoute me={me}><Eventing me={me}/></LoggedInRoute>}/>
<Route exact path='/new/jupyter' element={<LoggedInRoute me={me}><Jupyter/></LoggedInRoute>}/>
<Route exact path='/new/hasura' element={<LoggedInRoute me={me}><Hasura/></LoggedInRoute>}/>
</Routes>
</React.Suspense>
</div>
</div>
</div>
@@ -730,8 +730,8 @@ export function TopAppBarVertical(props) {
const [serverName, setServerName] = React.useState("...");
useQuery(GET_SETTINGS, {fetchPolicy: "no-cache",
onCompleted: (data) => {
setServerVersion(data.getGlobalSettings.settings["server_config"]["version"]);
setServerName(data.getGlobalSettings.settings["server_config"]["name"]);
setServerVersion(data.getGlobalSettings?.settings?.["server_config"]?.["version"]);
setServerName(data.getGlobalSettings?.settings?.["server_config"]?.["name"]);
}
});
const toggleDrawerOpen = (e) => {
@@ -841,11 +841,13 @@ export function TopAppBarVertical(props) {
borderRadius: theme.shape.borderRadius,
}}>
<StyledListItemIcon>
<img src={ReactLogo} onClick={()=>navigate('/new')} width={"32px"} height={"28px"} alt="Mythic" style={{cursor: "pointer"}}/>
<img src={ReactLogo} onClick={()=>navigate('/new')} width={"35px"} height={"35px"} alt="Mythic" style={{cursor: "pointer"}}/>
</StyledListItemIcon>
<ListItemText style={{margin: 0}} primary={
<>
<Typography style={{ fontSize: 12, color: theme.navigation.muted, display: "inline-block", lineHeight: 1.35}}>
<Typography style={{ fontSize: 12, color: theme.navigation.muted, display: "inline-block",
marginLeft: "1rem",
lineHeight: 1.35}}>
<b>Mythic:</b> v{serverVersion}<br/>
<b>UI:</b> v{mythicUIVersion}<br/>
</Typography>
@@ -267,7 +267,7 @@ const C2CollapsedGroupEdgeDialog = ({details, onClose, onOpenCallbackTasking}) =
};
return (
<>
<DialogTitle className="mythic-c2-action-title">
<DialogTitle className="mythic-accent-dialog-title">
<Typography component="div" className="mythic-c2-action-title-text">
{details?.groupLabel || "Callback Group"} Route Summary
</Typography>
@@ -385,7 +385,7 @@ const C2ManualRemoveEdgeDialog = ({options = [], onSubmit, onClose}) => {
};
return (
<>
<DialogTitle className="mythic-c2-action-title">
<DialogTitle className="mythic-accent-dialog-title">
<Typography component="div" className="mythic-c2-action-title-text">
Remove Active Edge
</Typography>
@@ -444,7 +444,7 @@ const C2SelectLinkCommandDialog = ({options = [], callback, onSubmit, onClose})
};
return (
<>
<DialogTitle className="mythic-c2-action-title">
<DialogTitle className="mythic-accent-dialog-title">
<Typography component="div" className="mythic-c2-action-title-text">
Select Link Command
</Typography>
@@ -515,9 +515,9 @@ export const CallbacksTableSleepCell = React.memo( ({rowData, cellData, updateSl
}, areEqual)
export const CallbacksTableTagsCell = React.memo(({rowData, cellData}) => {
return (
<div className="mythic-callback-tagsCell">
<div className="mythic-tag-cell mythic-tag-cell-fill">
<ViewEditTags target_object={"callback_id"} target_object_id={rowData.id} />
<div className="mythic-callback-tagsList">
<div className="mythic-tag-list mythic-callback-tag-list">
{cellData?.length > 0 ?
<TagsDisplay tags={cellData} expand={false} /> :
<span className="mythic-callback-tagsEmpty">No tags</span>
@@ -1328,7 +1328,7 @@ const ProcessBrowserInspector = ({nodeData, rowData, treeRootData, host, group,
target_object_id={nodeData.id || 0}
me={me} />
}
<div className="mythic-process-tags-list">
<div className="mythic-tag-list mythic-tag-list-truncate">
<TagsDisplay tags={nodeData?.tags || []} />
</div>
</div>
@@ -1402,12 +1402,12 @@ const FileBrowserTagsCell = ({rowData, treeRootData, host, me}) => {
const nodeData = treeRootData?.[host]?.[rowData["full_path_text"]];
return (
nodeData?.id ? (
<div className="mythic-process-tags-cell">
<div className="mythic-tag-cell mythic-tag-cell-fill">
<ViewEditTags
target_object={"mythictree_id"}
target_object_id={nodeData?.id || 0}
me={me} />
<div className="mythic-process-tags-list">
<div className="mythic-tag-list mythic-tag-list-truncate">
<TagsDisplay tags={nodeData?.tags || []} />
</div>
</div>
@@ -206,16 +206,16 @@ export function CallbacksTabsTaskingFilterDialog(props) {
return (
<Box className="mythic-tasking-filter-dialog">
<DialogTitle id="mythic-draggable-title" className="mythic-tasking-filter-dialog-title">
<Box className="mythic-tasking-filter-title-row">
<Box className="mythic-tasking-filter-title-icon">
<DialogTitle id="mythic-draggable-title" className="mythic-accent-dialog-title">
<Box className="mythic-accent-dialog-title-row">
<Box className="mythic-accent-dialog-title-icon">
<FilterAltIcon fontSize="small" />
</Box>
<Box sx={{minWidth: 0}}>
<Typography component="div" className="mythic-tasking-filter-title-main">
Task visibility filters
</Typography>
<Typography component="div" className="mythic-tasking-filter-title-subtitle">
<Typography component="div" className="mythic-accent-dialog-title-subtitle">
Control which tasks are shown for this callback.
</Typography>
</Box>
@@ -249,7 +249,7 @@ export function CallbacksTabsTaskingFilterDialog(props) {
emptyLabel="Any operator"
/>
</MythicFormField>
<Box className="mythic-tasking-filter-switch-stack">
<Box className="mythic-column-stack">
<MythicFormSwitchRow
label="Only tasks with comments"
description="Require at least one comment."
@@ -133,6 +133,20 @@ const GetCommandName = (task) => {
return task.command_name;
}
}
const GetTaskHistoryDuplicateKey = (task) => {
const commandName = task?.command_name || task?.command?.cmd || "";
return `${commandName}\u0000${task?.original_params || ""}`;
}
const CollapseAdjacentDuplicateTaskHistory = (tasks) => {
const collapsedTasks = [];
tasks.forEach((task) => {
const previousTask = collapsedTasks[collapsedTasks.length - 1];
if(previousTask === undefined || GetTaskHistoryDuplicateKey(previousTask) !== GetTaskHistoryDuplicateKey(task)){
collapsedTasks.push(task);
}
});
return collapsedTasks;
}
const GetDefaultValueForType = (parameter_type) => {
switch(parameter_type){
case "string":
@@ -556,7 +570,7 @@ export function CallbacksTabsTaskingInputPreMemo(props){
}, [...taskOptions.current]);
newTasks.sort((a,b) => a.id > b.id ? -1 : 1);
taskOptions.current= newTasks;
const filteredOptions = newTasks.filter( c => applyFilteringToTasks(c));
const filteredOptions = CollapseAdjacentDuplicateTaskHistory(newTasks.filter( c => applyFilteringToTasks(c)));
setFilteredTaskOptions(filteredOptions);
}
});
@@ -580,7 +594,7 @@ export function CallbacksTabsTaskingInputPreMemo(props){
});
React.useEffect( () => {
//console.log("filter updated")
const filteredOptions = taskOptions.current.filter( c => applyFilteringToTasks(c));
const filteredOptions = CollapseAdjacentDuplicateTaskHistory(taskOptions.current.filter( c => applyFilteringToTasks(c)));
setFilteredTaskOptions(filteredOptions);
let active = false;
if(props.filterOptions?.commandsList?.length > 0){
@@ -121,7 +121,7 @@ export function ManuallyAddEdgeDialog(props) {
if (loading) {
return (
<>
<DialogTitle className="mythic-c2-action-title">Add P2P Edge</DialogTitle>
<DialogTitle className="mythic-accent-dialog-title">Add P2P Edge</DialogTitle>
<DialogContent dividers={true}>
<LinearProgress />
</DialogContent>
@@ -132,7 +132,7 @@ export function ManuallyAddEdgeDialog(props) {
console.error(error);
return (
<>
<DialogTitle className="mythic-c2-action-title">Add P2P Edge</DialogTitle>
<DialogTitle className="mythic-accent-dialog-title">Add P2P Edge</DialogTitle>
<DialogContent dividers={true}>
<MythicDialogBody>
<MythicDialogSection title="Unable to load edge options" description={error.message} />
@@ -146,7 +146,7 @@ export function ManuallyAddEdgeDialog(props) {
}
return (
<>
<DialogTitle className="mythic-c2-action-title">
<DialogTitle className="mythic-accent-dialog-title">
<div className="mythic-dialog-title-row">
<div>
<Typography component="div" className="mythic-c2-action-title-text">
@@ -1030,16 +1030,16 @@ export function TaskParametersDialog(props) {
const commandName = commandInfo.cmd || props.command?.cmd || "Command";
return (
<React.Fragment>
<DialogTitle id="mythic-draggable-title" className="mythic-task-parameters-title">
<Box className="mythic-task-parameters-title-row">
<Box className="mythic-task-parameters-title-icon">
<DialogTitle id="mythic-draggable-title" className="mythic-accent-dialog-title">
<Box className="mythic-accent-dialog-title-row">
<Box className="mythic-accent-dialog-title-icon">
<TerminalIcon fontSize="small" />
</Box>
<Box className="mythic-task-parameters-title-copy">
<Typography component="div" className="mythic-task-parameters-title-main">
{commandName}
</Typography>
<Typography component="div" className="mythic-task-parameters-title-subtitle">
<Typography component="div" className="mythic-accent-dialog-title-subtitle">
Command parameters
</Typography>
</Box>
@@ -880,7 +880,7 @@ export function TaskParametersDialogRow(props){
</Typography>
<Switch checked={treatNewlinesAsNewEntries} onChange={toggleTreatNewlinesAsNewEntries} color={"info"} />
</Box>
<Box className="mythic-task-array-list">
<Box className="mythic-column-stack">
{arrayValue.map( (a, i) => (
<Box className="mythic-task-array-entry" key={'array' + props.name + i}>
<MythicStyledTooltip title={"Remove array element"}>
@@ -912,7 +912,7 @@ export function TaskParametersDialogRow(props){
</Typography>
<Switch checked={treatNewlinesAsNewEntries} onChange={toggleTreatNewlinesAsNewEntries} color={"info"} />
</Box>
<Box className="mythic-task-array-list">
<Box className="mythic-column-stack">
{typedArrayValue.map( (a, i) => (
<Box className="mythic-task-typed-array-entry" key={'typedarray' + props.name + i}>
<MythicStyledTooltip title={"Remove typed array element"}>
@@ -1046,7 +1046,7 @@ export function TaskParametersDialogRow(props){
return (
<Box className="mythic-task-agent-connect-editor">
{openAdditionalPayloadOnHostMenu ? (
<Box className="mythic-task-agent-connect-panel">
<Box className="mythic-task-agent-connect-panel mythic-column-stack">
<ParameterFieldRow label="Hostname">
<MythicTextField requiredValue={true} placeholder={"hostname"} value={agentConnectNewHost} multiline={false} autoFocus={props.autoFocus}
onChange={onChangeAgentConnectNewHost} display="inline-block" marginBottom={"0px"}/>
@@ -1087,7 +1087,7 @@ export function TaskParametersDialogRow(props){
</Box>
</Box>
) : (
<Box className="mythic-task-agent-connect-panel">
<Box className="mythic-task-agent-connect-panel mythic-column-stack">
<ParameterFieldRow label="Host">
{agentConnectHostOptions.length === 0 ? (
<ParameterEmptyInline>No hosts available</ParameterEmptyInline>
@@ -1183,7 +1183,7 @@ export function TaskParametersDialogRow(props){
<Typography component="div" className="mythic-task-agent-connect-parameters-title">
C2 profile parameters
</Typography>
<Box className="mythic-task-agent-connect-parameter-list">
<Box className="mythic-column-stack">
{(selectedC2Profile.parameters || []).map( (opt, i) => (
<Box className="mythic-task-agent-connect-parameter" key={"agentconnectparameters" + props.name + i}>
<Typography component="div" className="mythic-task-agent-connect-parameter-name">
@@ -1202,7 +1202,7 @@ export function TaskParametersDialogRow(props){
}
case "CredentialJson":
return (
<Box className="mythic-task-credential-editor">
<Box className="mythic-column-stack">
{createCredentialDialogOpen &&
<MythicDialog fullWidth={true} maxWidth="md" open={createCredentialDialogOpen}
onClose={()=>{setCreateCredentialDialogOpen(false);}}
@@ -13,11 +13,11 @@ export function CreatePayloadBuildParametersTable(props){
<div className="mythic-create-parameter-scroll">
{buildParameters.map(b => (
b.parameters.length > 0 &&
<section className="mythic-create-parameter-group" key={b?.name || 'undefined'}>
<section className="mythic-create-parameter-group mythic-column-stack" key={b?.name || 'undefined'}>
{b.name !== '' && b.name !== undefined &&
<div className="mythic-create-parameter-group-header">{b.name}</div>
}
<div className="mythic-create-parameter-list">
<div className="mythic-column-stack">
{b.parameters.map( (op) => (
<CreatePayloadParameter
displayMode="card"
@@ -8,7 +8,7 @@ export function CreatePayloadC2ProfileParametersTable(props){
props.onChange(props.name, paramName, value, error);
}
return (
<div className="mythic-create-parameter-list">
<div className="mythic-column-stack">
{props.c2profileparameters.map( (op) => (
<CreatePayloadParameter
displayMode="card"
@@ -101,7 +101,7 @@ export function Step3SelectCommands(props){
setCommandOptions(allCommands);
if(props.prevData !== undefined){
const selectedCommands = allCommands.map( (c) => {
if(props.prevData.includes(c.cmd)){
if(props.prevData.includes(c.cmd) || c?.attributes?.builtin){
return {...c, selected: true};
}else{
return {...c, selected: false};
@@ -5,7 +5,7 @@ import {CreatePayloadParameter} from '../CreatePayload/CreatePayloadParameter';
export function CreatePayloadBuildParametersTable(props){
return (
<div className="mythic-create-parameter-list">
<div className="mythic-column-stack">
{props.buildParameters.map( (op) => (
<CreatePayloadParameter displayMode="card" key={"buildparamtablerow" + op.id} onChange={props.onChange} {...op} />
))}
@@ -905,7 +905,7 @@ const CreateEventingStep1 = ({finished, back, first, last, cancel, prevData}) =>
<div className="mythic-eventing-metadata-field">
<div className="mythic-eventing-metadata-label">Trigger data</div>
{triggerOptionsData[trigger]?.trigger_data?.length > 0 ?
(<div className="mythic-eventing-trigger-parameter-list">
(<div className="mythic-eventing-trigger-parameter-list mythic-column-stack">
{triggerData?.map( t => (
<CreatePayloadParameter key={t.name} onChange={onChangeTriggerData} displayMode="card" {...t} />
))}
@@ -1005,7 +1005,7 @@ const EventingActionDataShell = ({children}) => (
<Typography component="div" className="mythic-eventing-step-help-text">
{eventingActionDataHelp}
</Typography>
<div className="mythic-eventing-action-data-list">
<div className="mythic-column-stack">
{children}
</div>
</>
@@ -854,7 +854,7 @@ function EventStepRender({selectedEventGroup, useSuppliedData}) {
{selectedEventGroup.id > 0 &&
<div className="mythic-eventing-flow-badge">Event group {selectedEventGroup.id}</div>
}
<Controls showInteractive={false} className="mythic-eventing-flow-controls mythic-graph-controls">
<Controls showInteractive={false} className="mythic-graph-controls-muted-hover">
</Controls>
</ReactFlow>
{openContextMenu && typeof document !== "undefined" && createPortal(
@@ -1159,7 +1159,7 @@ function EventStepInstanceRender({selectedEventGroupInstance}) {
onNodeContextMenu={onNodeContextMenu}
>
<div className="mythic-eventing-flow-badge">Instance {selectedEventGroupInstance}</div>
<Controls showInteractive={false} className="mythic-eventing-flow-controls mythic-graph-controls">
<Controls showInteractive={false} className="mythic-graph-controls-muted-hover">
</Controls>
</ReactFlow>
{openContextMenu && typeof document !== "undefined" && createPortal(
@@ -76,7 +76,7 @@ export function InstalledServiceMetadataSummary({items = [], description}) {
);
};
return (
<div className="mythic-installed-service-metadata-summary">
<div className="mythic-column-stack">
{visibleItems.length > 0 &&
<div className="mythic-installed-service-metadata-grid">
{visibleItems.map((item) => (
@@ -10,7 +10,7 @@ import {ImportPayloadConfigDialog} from './ImportPayloadConfigDialog';
import { MythicDialog } from '../../MythicComponents/MythicDialog';
import {useNavigate} from 'react-router-dom';
import { Backdrop } from '@mui/material';
import {MythicPageHeader, MythicPageHeaderChip} from "../../MythicComponents/MythicPageHeader";
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
import {MythicTablePagination} from "../../MythicComponents/MythicTablePagination";
import {MythicToolbarButton, MythicToolbarToggle} from "../../MythicComponents/MythicTableToolbar";
import {MythicLoadingState, MythicTableEmptyState} from "../../MythicComponents/MythicStateDisplay";
@@ -1,7 +1,7 @@
import React from 'react';
import {ReportingTable} from './ReportingTable';
import {MythicPageBody} from "../../MythicComponents/MythicPageBody";
import {MythicPageHeader, MythicPageHeaderChip} from "../../MythicComponents/MythicPageHeader";
import {MythicPageHeader} from "../../MythicComponents/MythicPageHeader";
import SummarizeIcon from '@mui/icons-material/Summarize';
@@ -12,12 +12,6 @@ export function Reporting() {
icon={<SummarizeIcon fontSize="small" />}
title={"Mythic Report Generation"}
subtitle={"Generate operation reports from Mythic data and selected report sections."}
meta={
<>
<MythicPageHeaderChip label="HTML" />
<MythicPageHeaderChip label="JSON" />
</>
}
/>
<ReportingTable />
</MythicPageBody>
@@ -210,11 +210,6 @@ export function ReportingTable(){
Report sections, output format, and callback exclusions.
</Typography>
</Box>
<Box sx={{alignItems: "center", display: "flex", flexWrap: "wrap", gap: 0.5}}>
<MythicPageHeaderChip label={selectedOutputFormat.toUpperCase()} status="info" />
<MythicPageHeaderChip label={`${enabledSectionCount} optional ${enabledSectionCount === 1 ? "section" : "sections"}`} status={enabledSectionCount > 0 ? "success" : "neutral"} />
{hasExclusions && <MythicPageHeaderChip label="Exclusions active" status="warning" />}
</Box>
</Box>
<MythicTableToolbar>
@@ -228,14 +223,6 @@ export function ReportingTable(){
))}
</MythicToolbarSelect>
</MythicTableToolbarGroup>
<MythicTableToolbarGroup grow label="Current selection">
<Box sx={{alignItems: "center", display: "flex", flexWrap: "wrap", gap: 0.45}}>
<MythicPageHeaderChip icon={<ArticleOutlinedIcon />} label={`${selectedOutputFormat.toUpperCase()} report`} />
{includeMITREPerTask && <MythicPageHeaderChip label="MITRE per task" status="success" />}
{includeMITREOverview && <MythicPageHeaderChip label="MITRE overview" status="success" />}
{includeOutput && <MythicPageHeaderChip label="Command output" status="success" />}
</Box>
</MythicTableToolbarGroup>
<MythicTableToolbarGroup label="Actions">
<MythicToolbarButton
className="mythic-toolbar-button-hover-success"
@@ -197,9 +197,9 @@ function CustomBrowserTableRow(props){
</div>
</MythicStyledTableCell>
<MythicStyledTableCell>
<div className="mythic-custom-browser-tags-cell">
<div className="mythic-tag-cell">
<ViewEditTags target_object={"mythictree_id"} target_object_id={props.id} me={me} />
<div className="mythic-custom-browser-tags-list">
<div className="mythic-tag-list mythic-tag-list-truncate">
<TagsDisplay tags={props.tags} />
</div>
</div>
@@ -1,39 +1,100 @@
export function copyStringToClipboard(str) {
let el = document.createElement('textarea');
function copyStringToClipboardWithExecCommand(text) {
let copyTarget = null;
const selectedRanges = [];
const selection = document.getSelection();
const activeElement = document.activeElement;
const onCopy = (event) => {
if(event.clipboardData){
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
try {
event.clipboardData.clearData();
} catch {
// Some browsers expose setData without allowing clearData during copy.
}
event.clipboardData.setData("text/plain", text);
}
};
try {
// Create new element
// Set value (string to be copied)
el.value = str;
if(el.value === ""){
el.value = " ";
if(selection){
for(let i = 0; i < selection.rangeCount; i++){
selectedRanges.push(selection.getRangeAt(i).cloneRange());
}
}
// Set non-editable to avoid focus and move outside of view
el.setAttribute('readonly', '');
el.style = {position: 'absolute', left: '-9999px'};
document.body.appendChild(el);
// Select text inside element
el.select();
// Copy text to clipboard
let success = document.execCommand('copy');
if(!success){
console.log("failed to copy data");
copyTarget = document.createElement('span');
copyTarget.textContent = text;
copyTarget.setAttribute('aria-hidden', 'true');
copyTarget.style.position = 'fixed';
copyTarget.style.top = '0';
copyTarget.style.left = '0';
copyTarget.style.width = '1px';
copyTarget.style.height = '1px';
copyTarget.style.margin = '-1px';
copyTarget.style.padding = '0';
copyTarget.style.border = '0';
copyTarget.style.overflow = 'hidden';
copyTarget.style.clip = 'rect(0, 0, 0, 0)';
copyTarget.style.whiteSpace = 'pre';
copyTarget.style.userSelect = 'text';
copyTarget.style.webkitUserSelect = 'text';
copyTarget.style.MozUserSelect = 'text';
document.body.appendChild(copyTarget);
if(selection){
const copyRange = document.createRange();
copyRange.selectNodeContents(copyTarget);
selection.removeAllRanges();
selection.addRange(copyRange);
}
success = document.execCommand('cut');
if(!success){
console.log("failed to cut data");
}
navigator?.clipboard?.writeText(el.value);
// Remove temporary element
document.body.removeChild(el);
return true;
document.addEventListener("copy", onCopy, true);
return document.execCommand('copy');
} catch (error) {
document.body.removeChild(el);
console.log("warning", "Failed to copy to clipboard: " + error.toString());
console.log("warning", "Failed to copy to clipboard with fallback: " + error.toString());
return false;
} finally {
document.removeEventListener("copy", onCopy, true);
if(copyTarget?.parentNode){
copyTarget.parentNode.removeChild(copyTarget);
}
if(selection){
try {
selection.removeAllRanges();
selectedRanges.forEach((range) => selection.addRange(range));
} catch (error) {
console.log("warning", "Failed to restore selection after clipboard copy: " + error.toString());
}
}
if(activeElement?.focus){
try {
activeElement.focus({preventScroll: true});
} catch (error) {
activeElement.focus();
}
}
}
}
export function copyStringToClipboard(str) {
const text = str === undefined || str === null || String(str) === "" ? " " : String(str);
let clipboardApiStarted = false;
try {
if(navigator?.clipboard?.writeText){
clipboardApiStarted = true;
navigator.clipboard.writeText(text).catch((error) => {
console.log("warning", "Failed to copy to clipboard with Clipboard API: " + error.toString());
});
}
} catch (error) {
console.log("warning", "Failed to start Clipboard API copy: " + error.toString());
}
const fallbackSuccess = copyStringToClipboardWithExecCommand(text);
if(clipboardApiStarted || fallbackSuccess){
return true;
}
console.log("failed to copy data");
return false;
}
export function downloadFileFromMemory(output, filename){
const dataBlob = new Blob([output], {type: 'application/octet-stream'});
const ele = document.getElementById("download_config");
+43 -319
View File
@@ -823,8 +823,7 @@ tspan {
word-break: break-word;
}
.mythic-process-name-cell,
.mythic-process-string-cell,
.mythic-process-tags-cell {
.mythic-process-string-cell {
align-items: center;
display: flex;
height: 100%;
@@ -884,10 +883,18 @@ tspan {
padding-left: 0.25rem;
padding-right: 0.25rem;
}
.mythic-process-tags-cell {
.mythic-tag-cell {
align-items: center;
display: flex;
gap: 0.35rem;
min-width: 0;
overflow: hidden;
}
.mythic-process-tags-cell .MuiIconButton-root {
.mythic-tag-cell-fill {
height: 100%;
width: 100%;
}
.mythic-tag-cell .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};
@@ -902,15 +909,15 @@ tspan {
transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
width: 22px;
}
.mythic-process-tags-cell .MuiIconButton-root:hover {
.mythic-tag-cell .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-process-tags-cell .MuiIconButton-root svg {
.mythic-tag-cell .MuiIconButton-root svg {
font-size: 0.92rem;
}
.mythic-process-tags-list {
.mythic-tag-list {
align-items: center;
display: flex;
flex: 1 1 auto;
@@ -919,21 +926,23 @@ tspan {
min-width: 0;
overflow: hidden;
}
.mythic-process-tags-list .MuiChip-root {
.mythic-tag-list .MuiChip-root {
flex: 0 0 auto;
float: none !important;
height: 18px !important;
max-width: 5.5rem;
}
.mythic-process-tags-list .MuiChip-label {
.mythic-tag-list .MuiChip-label {
font-size: 0.68rem;
font-weight: 800;
min-width: 0;
overflow: hidden !important;
padding-left: 0.35rem;
padding-right: 0.35rem;
text-overflow: ellipsis;
}
.mythic-tag-list-truncate .MuiChip-label {
min-width: 0;
overflow: hidden !important;
}
.mythic-process-action-button.MuiIconButton-root {
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
border-radius: ${(props) => props.theme.shape.borderRadius}px;
@@ -1434,7 +1443,7 @@ tspan {
background-color: ${(props) => props.theme.surfaces?.raised || props.theme.palette.background.paper};
color: ${(props) => props.theme.palette.text.primary};
}
.mythic-tasking-filter-dialog-title.MuiDialogTitle-root {
.mythic-accent-dialog-title.MuiDialogTitle-root {
background-image: ${getSectionHeaderGradient} !important;
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
color: ${(props) => props.theme.pageHeaderText?.main || props.theme.palette.text.primary};
@@ -1442,7 +1451,10 @@ tspan {
padding: 1rem 1.15rem !important;
position: relative;
}
.mythic-tasking-filter-dialog-title.MuiDialogTitle-root::before {
.mythic-accent-dialog-title.MuiDialogTitle-root::before,
.mythic-detail-section-header::before,
.mythic-section-header::before,
.mythic-dashboard-edit-toolbar::before {
background-color: ${getSectionHeaderAccent};
bottom: 0;
box-shadow: 0 0 0 1px ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.2)};
@@ -1452,7 +1464,7 @@ tspan {
top: 0;
width: 6px;
}
.mythic-tasking-filter-title-row {
.mythic-accent-dialog-title-row {
align-items: center;
display: flex;
gap: 0.8rem;
@@ -1460,7 +1472,7 @@ tspan {
min-height: 42px;
padding-left: 0.55rem;
}
.mythic-tasking-filter-title-icon {
.mythic-accent-dialog-title-icon {
align-items: center;
background-color: ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.12)};
border: 1px solid ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.24)};
@@ -1477,7 +1489,7 @@ tspan {
font-weight: 850;
line-height: 1.15;
}
.mythic-tasking-filter-title-subtitle {
.mythic-accent-dialog-title-subtitle {
color: ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.78)};
font-size: 0.76rem;
font-weight: 600;
@@ -1530,7 +1542,7 @@ tspan {
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
color: ${(props) => props.theme.palette.text.secondary};
}
.mythic-tasking-filter-switch-stack {
.mythic-column-stack {
display: flex;
flex-direction: column;
gap: 0.55rem;
@@ -1632,43 +1644,6 @@ tspan {
grid-template-columns: 1fr;
}
}
.mythic-task-parameters-title.MuiDialogTitle-root {
background-image: ${getSectionHeaderGradient} !important;
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
color: ${(props) => props.theme.pageHeaderText?.main || props.theme.palette.text.primary};
cursor: move;
padding: 1rem 1.15rem !important;
position: relative;
}
.mythic-task-parameters-title.MuiDialogTitle-root::before {
background-color: ${getSectionHeaderAccent};
bottom: 0;
box-shadow: 0 0 0 1px ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.2)};
content: "";
left: 0;
position: absolute;
top: 0;
width: 6px;
}
.mythic-task-parameters-title-row {
align-items: center;
display: flex;
gap: 0.8rem;
min-height: 42px;
min-width: 0;
padding-left: 0.55rem;
}
.mythic-task-parameters-title-icon {
align-items: center;
background-color: ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.12)};
border: 1px solid ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.24)};
border-radius: ${(props) => props.theme.shape.borderRadius}px;
display: inline-flex;
flex: 0 0 auto;
height: 32px;
justify-content: center;
width: 32px;
}
.mythic-task-parameters-title-copy {
flex: 1 1 auto;
min-width: 12rem;
@@ -1680,13 +1655,6 @@ tspan {
line-height: 1.15;
overflow-wrap: anywhere;
}
.mythic-task-parameters-title-subtitle {
color: ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.78)};
font-size: 0.76rem;
font-weight: 600;
line-height: 1.25;
margin-top: 0.15rem;
}
.mythic-task-parameters-title-meta {
align-items: center;
display: flex;
@@ -2033,14 +2001,6 @@ tspan {
gap: 0.65rem;
min-width: 0;
}
.mythic-task-array-list,
.mythic-task-agent-connect-panel,
.mythic-task-agent-connect-parameter-list {
display: flex;
flex-direction: column;
gap: 0.55rem;
min-width: 0;
}
.mythic-task-array-entry,
.mythic-task-typed-array-entry {
align-items: center;
@@ -2116,12 +2076,6 @@ tspan {
white-space: pre-wrap;
word-break: break-word;
}
.mythic-task-credential-editor {
display: flex;
flex-direction: column;
gap: 0.55rem;
min-width: 0;
}
.mythic-task-credential-actions {
align-items: center;
display: flex;
@@ -2173,12 +2127,12 @@ tspan {
padding: 0.85rem 1rem !important;
}
@media screen and (max-width: 900px) {
.mythic-task-parameters-title-row,
.mythic-accent-dialog-title-row,
.mythic-task-parameters-group-card,
.mythic-task-parameter-card {
grid-template-columns: 1fr;
}
.mythic-task-parameters-title-row {
.mythic-accent-dialog-title-row {
align-items: flex-start;
flex-wrap: wrap;
}
@@ -2871,60 +2825,8 @@ 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 {
.mythic-callback-tag-list .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};
@@ -3650,24 +3552,6 @@ tspan {
padding: 0.22rem 0.48rem;
text-transform: none;
}
.mythic-c2-action-title.MuiDialogTitle-root {
background-image: ${getSectionHeaderGradient} !important;
border-bottom: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
color: ${(props) => props.theme.pageHeaderText?.main || props.theme.palette.text.primary};
cursor: move;
padding: 1rem 1.15rem !important;
position: relative;
}
.mythic-c2-action-title.MuiDialogTitle-root::before {
background-color: ${getSectionHeaderAccent};
bottom: 0;
box-shadow: 0 0 0 1px ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.2)};
content: "";
left: 0;
position: absolute;
top: 0;
width: 6px;
}
.mythic-c2-action-title-text.MuiTypography-root {
color: ${(props) => props.theme.pageHeaderText?.main || props.theme.palette.text.primary};
font-size: 1rem;
@@ -4565,16 +4449,6 @@ tspan {
position: relative;
width: 100%;
}
.mythic-detail-section-header::before {
background-color: ${getSectionHeaderAccent};
bottom: 0;
box-shadow: 0 0 0 1px ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.2)};
content: "";
left: 0;
position: absolute;
top: 0;
width: 6px;
}
.mythic-section-header {
background-image: ${getSectionHeaderGradient} !important;
border-color: ${(props) => alpha(getSectionHeaderAccent(props), props.theme.palette.mode === "dark" ? 0.55 : 0.38)} !important;
@@ -4583,16 +4457,6 @@ tspan {
padding-left: 1rem !important;
position: relative !important;
}
.mythic-section-header::before {
background-color: ${getSectionHeaderAccent};
bottom: 0;
box-shadow: 0 0 0 1px ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.2)};
content: "";
left: 0;
position: absolute;
top: 0;
width: 6px;
}
.mythic-detail-section-title {
color: ${(props) => props.theme.pageHeaderText?.main || props.theme.palette.text.primary};
font-size: 1.25rem;
@@ -6332,12 +6196,6 @@ tspan {
overflow: auto;
width: 100%;
}
.mythic-create-parameter-group {
display: flex;
flex-direction: column;
gap: 0.55rem;
min-width: 0;
}
.mythic-create-parameter-group-header {
background-image: ${getSectionHeaderGradient};
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
@@ -6385,12 +6243,6 @@ tspan {
padding-left: 0.65rem;
white-space: pre-wrap;
}
.mythic-create-parameter-list {
display: flex;
flex-direction: column;
gap: 0.55rem;
min-width: 0;
}
.mythic-create-parameter-card {
background-color: ${(props) => props.theme.palette.background.paper};
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
@@ -6637,7 +6489,8 @@ tspan {
.mythic-dashboard-card-body-empty {
padding: 0.75rem;
}
.mythic-dashboard-icon-button {
.mythic-dashboard-icon-button,
.mythic-table-row-icon-action {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.035)"} !important;
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
border-radius: ${(props) => props.theme.shape.borderRadius}px !important;
@@ -6647,7 +6500,8 @@ tspan {
padding: 0;
width: 30px;
}
.mythic-dashboard-icon-button:hover {
.mythic-dashboard-icon-button:hover,
.mythic-table-row-icon-action:hover {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.06)"} !important;
border-color: ${(props) => props.theme.table?.border || props.theme.borderColor} !important;
}
@@ -6662,7 +6516,10 @@ tspan {
border-color: ${(props) => props.theme.palette.info.main + "88"} !important;
color: ${(props) => props.theme.palette.info.main} !important;
}
.mythic-dashboard-icon-button.Mui-disabled {
.mythic-dashboard-icon-button.Mui-disabled,
.mythic-dashboard-table-action.Mui-disabled,
.mythic-table-row-action.Mui-disabled,
.mythic-table-row-icon-action.Mui-disabled {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)"} !important;
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
color: ${(props) => props.theme.palette.text.disabled} !important;
@@ -6675,7 +6532,8 @@ tspan {
min-width: 5.6rem !important;
}
.mythic-dashboard-primary-button,
.mythic-dashboard-table-action {
.mythic-dashboard-table-action,
.mythic-table-row-action {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.035)"} !important;
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
border-radius: ${(props) => props.theme.shape.borderRadius}px !important;
@@ -6703,11 +6561,6 @@ tspan {
border-color: ${(props) => props.theme.palette.error.main + "88"} !important;
color: ${(props) => props.theme.palette.error.main} !important;
}
.mythic-dashboard-table-action.Mui-disabled {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)"} !important;
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
color: ${(props) => props.theme.palette.text.disabled} !important;
}
.mythic-dashboard-chart-body {
gap: 0.35rem;
padding: 0.5rem 0.55rem 0.55rem;
@@ -7268,16 +7121,6 @@ tspan {
padding: 0.52rem 0.65rem 0.52rem 1rem;
position: relative;
}
.mythic-dashboard-edit-toolbar::before {
background-color: ${getSectionHeaderAccent};
bottom: 0;
box-shadow: 0 0 0 1px ${(props) => alpha(props.theme.pageHeaderText?.main || props.theme.palette.text.primary, 0.2)};
content: "";
left: 0;
position: absolute;
top: 0;
width: 6px;
}
.mythic-dashboard-edit-toolbar-title {
color: inherit;
font-size: 0.78rem;
@@ -7551,17 +7394,6 @@ tspan {
gap: 0.35rem;
min-width: 0;
}
.mythic-table-row-action {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.035)"} !important;
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
border-radius: ${(props) => props.theme.shape.borderRadius}px !important;
box-shadow: none !important;
color: ${(props) => props.theme.palette.text.primary} !important;
font-size: 0.74rem;
font-weight: 750;
min-height: 30px;
text-transform: none;
}
.mythic-table-row-action:hover {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.06)"} !important;
border-color: ${(props) => props.theme.table?.border || props.theme.borderColor} !important;
@@ -7613,25 +7445,6 @@ tspan {
background-color: ${(props) => props.theme.palette.error.main + "2b"} !important;
border-color: ${(props) => props.theme.palette.error.main + "88"} !important;
}
.mythic-table-row-action.Mui-disabled {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)"} !important;
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
color: ${(props) => props.theme.palette.text.disabled} !important;
}
.mythic-table-row-icon-action {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.035)"} !important;
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
border-radius: ${(props) => props.theme.shape.borderRadius}px !important;
box-shadow: none !important;
color: ${(props) => props.theme.palette.text.primary} !important;
height: 30px;
padding: 0;
width: 30px;
}
.mythic-table-row-icon-action:hover {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.06)"} !important;
border-color: ${(props) => props.theme.table?.border || props.theme.borderColor} !important;
}
.mythic-table-row-icon-action-danger {
color: ${(props) => props.theme.palette.error.main} !important;
}
@@ -7839,60 +7652,6 @@ tspan {
text-overflow: ellipsis;
white-space: nowrap;
}
.mythic-custom-browser-tags-cell {
align-items: center;
display: flex;
gap: 0.35rem;
min-width: 0;
overflow: hidden;
}
.mythic-custom-browser-tags-cell .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-custom-browser-tags-cell .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-custom-browser-tags-cell .MuiIconButton-root svg {
font-size: 0.92rem;
}
.mythic-custom-browser-tags-list {
align-items: center;
display: flex;
flex: 1 1 auto;
flex-wrap: nowrap;
gap: 0.2rem;
min-width: 0;
overflow: hidden;
}
.mythic-custom-browser-tags-list .MuiChip-root {
flex: 0 0 auto;
float: none !important;
height: 18px !important;
max-width: 5.5rem;
}
.mythic-custom-browser-tags-list .MuiChip-label {
font-size: 0.68rem;
font-weight: 800;
min-width: 0;
overflow: hidden !important;
padding-left: 0.35rem;
padding-right: 0.35rem;
text-overflow: ellipsis;
}
.mythic-eventing-workspace {
display: flex;
flex: 1 1 auto;
@@ -8370,12 +8129,6 @@ tspan {
min-height: 1.65rem;
padding: 0 0.55rem;
}
.mythic-eventing-trigger-parameter-list {
display: flex;
flex-direction: column;
gap: 0.55rem;
min-width: 0;
}
.mythic-eventing-trigger-parameter-list .mythic-create-parameter-card {
grid-template-columns: minmax(14rem, 0.36fr) minmax(0, 1fr);
}
@@ -8953,12 +8706,6 @@ tspan {
margin: 0 0 0.65rem;
padding: 0.55rem 0.65rem;
}
.mythic-eventing-action-data-list {
display: flex;
flex-direction: column;
gap: 0.55rem;
min-width: 0;
}
.mythic-eventing-action-data-card {
background-color: ${(props) => props.theme.palette.background.paper};
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
@@ -9549,19 +9296,7 @@ tspan {
top: 0.75rem;
z-index: 5;
}
.mythic-eventing-flow-controls {
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;
box-shadow: 0 6px 18px ${(props) => alpha(props.theme.palette.common.black, props.theme.palette.mode === "dark" ? 0.22 : 0.08)} !important;
overflow: hidden;
}
.mythic-eventing-flow-controls .react-flow__controls-button {
background-color: ${(props) => props.theme.palette.background.paper};
border-bottom-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor};
color: ${(props) => props.theme.palette.text.secondary};
}
.mythic-eventing-flow-controls .react-flow__controls-button:hover {
.mythic-graph-controls-muted-hover .react-flow__controls-button:hover {
background-color: ${(props) => props.theme.tableHover};
color: ${(props) => props.theme.palette.text.primary};
}
@@ -10088,12 +9823,6 @@ tspan {
gap: 0.4rem;
min-width: 0;
}
.mythic-installed-service-metadata-summary {
display: flex;
flex-direction: column;
gap: 0.55rem;
min-width: 0;
}
.mythic-installed-service-metadata-grid {
display: grid;
gap: 0.32rem 0.7rem;
@@ -10519,11 +10248,6 @@ tspan {
background-color: ${(props) => props.theme.palette.error.main + "2b"} !important;
border-color: ${(props) => props.theme.palette.error.main + "88"} !important;
}
.mythic-table-row-icon-action.Mui-disabled {
background-color: ${(props) => props.theme.palette.mode === "dark" ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)"} !important;
border-color: ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
color: ${(props) => props.theme.palette.text.disabled} !important;
}
.MuiIconButton-root.mythic-response-action-button {
background-color: ${(props) => props.theme.palette.mode === "dark" ? alpha(props.theme.palette.common.white, 0.045) : alpha(props.theme.palette.common.black, 0.028)} !important;
border: 1px solid ${(props) => props.theme.table?.borderSoft || props.theme.borderColor} !important;
+1
View File
@@ -1326,3 +1326,4 @@ type custombrowserExportFunctionOutput {
status: String!
error: String
}
@@ -86,3 +86,11 @@ select_permissions:
- type
filter: {}
comment: ""
update_permissions:
- role: mythic_admin
permission:
columns:
- deleted
filter: {}
check: null
comment: ""
+6 -4
View File
@@ -23,10 +23,12 @@ var (
)
const (
ContextKeyClaims = "claims"
ContextKeyAPIToken = "apitoken_struct"
ContextKeyUserID = "user_id"
ContextKeyUsername = "username"
ContextKeyClaims = "claims"
ContextKeyAPIToken = "apitoken_struct"
ContextKeyUserID = "user_id"
ContextKeyUsername = "username"
ContextKeyAPITokenID = "apitoken_id"
ContextKeyOperatorOperationStruct = "operatorOperation_struct"
)
var (
@@ -38,7 +38,7 @@ func IPBlockMiddleware() gin.HandlerFunc {
c.Abort()
return
}
// make sure the ipAddr is in at least one of the alowed IP blocks
// make sure the ipAddr is in at least one of the allowed IP blocks
for _, subnet := range utils.MythicConfig.AllowedIPBlocks {
if subnet.Contains(ipAddr) {
c.Next()
@@ -71,8 +71,8 @@ func RBACMiddleware(allowedRoles []string) gin.HandlerFunc {
return
}
if operatorOperation.CurrentOperator.Admin || utils.SliceContains(allowedRoles, operatorOperation.ViewMode) {
c.Set("operatorOperation", operatorOperation)
c.Set("apitokens-id", customClaims.APITokensID)
c.Set(ContextKeyOperatorOperationStruct, operatorOperation)
c.Set(ContextKeyAPITokenID, customClaims.APITokensID)
c.Next()
return
}
@@ -146,6 +146,7 @@ func TokenScopeMiddleware(requiredScopes []string) gin.HandlerFunc {
return
}
}
SetRabbitMQAuthContextForGin(c)
c.Next()
}
}
@@ -0,0 +1,38 @@
package authentication
import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/rabbitmq"
)
const ContextKeyRabbitMQAuthContext = "rabbitmq_auth_context"
func SetRabbitMQAuthContextForGin(c *gin.Context) rabbitmq.RabbitMQAuthContext {
authContext := rabbitmq.RabbitMQAuthContext{
ContextID: uuid.NewString(),
}
if claims, err := GetClaims(c); err == nil && claims != nil {
authContext.OperatorID = claims.UserID
authContext.OperationID = claims.OperationID
authContext.APITokensID = claims.APITokensID
authContext.EventStepInstanceID = claims.EventStepInstanceID
effectiveScopes := mythicjwt.EffectiveScopes(claims.Scopes)
authContext.SourceScopes = append([]string{}, effectiveScopes...)
}
c.Set(ContextKeyRabbitMQAuthContext, authContext)
return authContext
}
func RabbitMQAuthContextFromGin(c *gin.Context) rabbitmq.RabbitMQAuthContext {
if c == nil {
return rabbitmq.RabbitMQAuthContext{}
}
if existingContext, ok := c.Get(ContextKeyRabbitMQAuthContext); ok {
if authContext, ok := existingContext.(rabbitmq.RabbitMQAuthContext); ok {
return authContext
}
}
return SetRabbitMQAuthContextForGin(c)
}
@@ -63,6 +63,7 @@ type Task struct {
InteractiveTaskType structs.NullInt64 `db:"interactive_task_type" json:"interactive_task_type"`
EventStepInstanceID structs.NullInt64 `db:"eventstepinstance_id" json:"event_step_instance_id" mapstructure:"event_step_instance_id"`
APITokensID structs.NullInt64 `db:"apitokens_id" json:"api_tokens_id" mapstructure:"apitokens_id"`
APIToken Apitokens `db:"apitoken" json:"apitoken"`
HasInterceptedResponse bool `db:"has_intercepted_response" json:"has_intercepted_response"`
CommandPayloadType string `db:"command_payload_type" json:"command_payload_type" mapstructure:"command_payload_type"`
ProcessAtOriginalCommand bool `db:"process_at_original_command" json:"process_at_original_command" mapstructure:"process_at_original_command"`
@@ -1,32 +0,0 @@
package rabbitmq
import (
"errors"
"sync"
"github.com/its-a-feature/Mythic/utils"
)
var containerAuthMap = make(map[string]string)
var containerAuthMapLock sync.RWMutex
func NewContainerAuth(containerName string) string {
containerAuthMapLock.Lock()
defer containerAuthMapLock.Unlock()
if _, ok := containerAuthMap[containerName]; ok {
return containerAuthMap[containerName]
}
containerAuthMap[containerName] = utils.GenerateRandomPassword(36)
return containerAuthMap[containerName]
}
func GetContainerFromAuth(password string) (string, error) {
containerAuthMapLock.RLock()
defer containerAuthMapLock.RUnlock()
for key, value := range containerAuthMap {
if value == password {
return key, nil
}
}
return "", errors.New("container not found")
}
@@ -235,7 +235,12 @@ func CreateGraphQLSpectatorAPITokenAndSendOnStartMessage(containerName string) {
}
onStartMessage.APIToken = plainAPITokenValue
go expireAPITokenAfterShortLivedTTL(apiToken.ID)
err = RabbitMQConnection.SendContainerOnStart(onStartMessage)
err = RabbitMQConnection.SendContainerOnStart(onStartMessage, RabbitMQAuthContext{
OperatorID: apiToken.OperatorID,
OperationID: onStartMessage.OperationID,
APITokensID: apiToken.ID,
SourceScopes: apiToken.Scopes,
})
if err != nil {
logging.LogError(err, "Failed to send container on start")
}
+2 -2
View File
@@ -7,6 +7,7 @@ type RPCRetryPolicy int
const (
MYTHIC_EXCHANGE = "mythic_exchange"
MYTHIC_TOPIC_EXCHANGE = "mythic_topic_exchange"
MYTHIC_RABBITMQ_AUTH_CONTEXT_HEADER = "mythic-auth-context"
RETRY_CONNECT_DELAY = 5 * time.Second
CHECK_CONTAINER_STATUS_DELAY = 10 * time.Second
TIME_FORMAT_STRING_YYYY_MM_DD = "2006-01-02"
@@ -178,7 +179,6 @@ const (
MYTHIC_RPC_FILE_SEARCH = "mythic_rpc_file_search"
MYTHIC_RPC_FILE_UPDATE = "mythic_rpc_file_update"
MYTHIC_RPC_FILE_GET_CONTENT = "mythic_rpc_file_get_content"
MYTHIC_RPC_FILE_REGISTER = "mythic_rpc_file_register"
// MYTHIC_RPC_PAYLOAD_CREATE_FROM_UUID payload operations
MYTHIC_RPC_PAYLOAD_CREATE_FROM_UUID = "mythic_rpc_payload_create_from_uuid"
MYTHIC_RPC_PAYLOAD_CREATE_FROM_SCRATCH = "mythic_rpc_payload_create_from_scratch"
@@ -260,7 +260,7 @@ const (
// Agent Message
MYTHIC_RPC_HANDLE_AGENT_JSON = "mythic_rpc_handle_agent_message_json"
// CustomBrowser
CUSTOMBROWSER_SEARCH = "mythic_rpc_custombrowser_search"
MYTHIC_RPC_CUSTOMBROWSER_SEARCH = "mythic_rpc_custombrowser_search"
// MYTHIC_RPC_BLANK blank
MYTHIC_RPC_BLANK = "mythic_rpc_blank"
)
@@ -45,6 +45,7 @@ func (r *rabbitMQConnection) EmitSiemMessage(loggingMessage LoggingMessage) erro
"",
localLoggingMessage,
true,
nil,
); err != nil {
//logging.LogError(err, "Failed to emit SIEM Message")
return err
@@ -77,6 +77,7 @@ func (r *rabbitMQConnection) EmitWebhookMessage(webhookMessage WebhookMessage) e
"",
localWebhookMessage,
true,
nil,
); err != nil {
//logging.LogError(err, "Failed to emit webhook Message")
return err
+28 -7
View File
@@ -749,6 +749,7 @@ func processEventFinishAndNextStepStart(eventNotification EventNotification) {
if err != nil {
logging.LogError(err, "Failed to mark apitoken as deleted")
}
InvalidateRabbitMQAuthContextsForEventStepInstance(triggeringStep.ID)
if !eventNotification.ActionSuccess && !triggeringStep.ContinueOnError {
markStepInstanceAsError(triggeringStep, eventNotification.ActionStderr)
return
@@ -1174,7 +1175,12 @@ func startEventStepInstanceActionCreatePayload(eventStepInstance databaseStructs
ID: eventStepInstance.OperationID,
},
}
_, _, err = RegisterNewPayload(eventData.PayloadConfiguration, &operatorOperation)
_, _, err = RegisterNewPayload(eventData.PayloadConfiguration, &operatorOperation, RabbitMQAuthContext{
OperatorID: eventStepInstance.OperatorID,
OperationID: eventStepInstance.OperationID,
EventStepInstanceID: eventStepInstance.ID,
SourceScopes: []string{mythicjwt.SCOPE_PAYLOAD_WRITE},
})
if err != nil {
logging.LogError(err, "Failed to register payload for CreatePayloadWebhook")
}
@@ -1199,8 +1205,7 @@ func startEventStepInstanceActionCreateCallback(eventStepInstance databaseStruct
logging.LogError(err, "Failed to decode action data")
return err
}
eventData.EventStepInstanceID = &eventStepInstance.ID
callbackCreateResponse := MythicRPCCallbackCreate(eventData)
callbackCreateResponse := MythicRPCCallbackCreate(eventData, rabbitMQAuthContextForEventStepInstance(eventStepInstance))
if !callbackCreateResponse.Success {
logging.LogError(nil, callbackCreateResponse.Error)
return errors.New(callbackCreateResponse.Error)
@@ -1245,6 +1250,12 @@ func startEventStepInstanceActionCreateTask(eventStepInstance databaseStructs.Ev
ParentTaskID: eventData.ParentTaskID,
IsInteractiveTask: eventData.IsInteractiveTask,
InteractiveTaskType: eventData.InteractiveTaskType,
AuthContext: RabbitMQAuthContext{
OperatorID: eventStepInstance.OperatorID,
OperationID: eventStepInstance.OperationID,
EventStepInstanceID: eventStepInstance.ID,
SourceScopes: []string{mythicjwt.SCOPE_TASK_WRITE},
},
}
if eventData.ParamDictionary != nil && len(eventData.ParamDictionary) > 0 {
paramsString, _ := json.Marshal(eventData.ParamDictionary)
@@ -1337,6 +1348,15 @@ func startEventStepInstanceActionSendWebhook(eventStepInstance databaseStructs.E
})
return nil
}
func rabbitMQAuthContextForEventStepInstance(eventStepInstance databaseStructs.EventStepInstance) RabbitMQAuthContext {
return RabbitMQAuthContext{
OperatorID: eventStepInstance.OperatorID,
OperationID: eventStepInstance.OperationID,
EventStepInstanceID: eventStepInstance.ID,
}
}
func startEventStepInstanceActionCustomFunction(eventStepInstance databaseStructs.EventStepInstance, inputs map[string]interface{},
actionDataMap map[string]interface{},
environment map[string]interface{}) error {
@@ -1359,7 +1379,7 @@ func startEventStepInstanceActionCustomFunction(eventStepInstance databaseStruct
ActionData: actionDataMap,
FunctionName: functionName,
ContainerName: containerName,
})
}, rabbitMQAuthContextForEventStepInstance(eventStepInstance))
}
func startEventStepInstanceActionConditionalCheck(eventStepInstance databaseStructs.EventStepInstance, inputs map[string]interface{},
actionDataMap map[string]interface{},
@@ -1386,7 +1406,7 @@ func startEventStepInstanceActionConditionalCheck(eventStepInstance databaseStru
ActionData: actionDataMap,
FunctionName: functionName,
ContainerName: containerName,
})
}, rabbitMQAuthContextForEventStepInstance(eventStepInstance))
}
func startEventStepInstanceActionInterceptTask(eventStepInstance databaseStructs.EventStepInstance, inputs map[string]interface{},
actionDataMap map[string]interface{},
@@ -1416,7 +1436,7 @@ func startEventStepInstanceActionInterceptTask(eventStepInstance databaseStructs
Inputs: inputs,
ActionData: actionDataMap,
ContainerName: containerName,
})
}, rabbitMQAuthContextForEventStepInstance(eventStepInstance))
}
func startEventStepInstanceActionInterceptResponse(eventStepInstance databaseStructs.EventStepInstance, inputs map[string]interface{},
actionDataMap map[string]interface{},
@@ -1440,7 +1460,7 @@ func startEventStepInstanceActionInterceptResponse(eventStepInstance databaseStr
Inputs: inputs,
ActionData: actionDataMap,
ContainerName: containerName,
})
}, rabbitMQAuthContextForEventStepInstance(eventStepInstance))
}
func restartFailedJobs(eventgroupInstanceID int) error {
_, err := database.DB.Exec(`UPDATE eventstepinstance
@@ -1596,6 +1616,7 @@ func markStepInstanceAsError(eventStepInstance databaseStructs.EventStepInstance
if err != nil {
logging.LogError(err, "Failed to mark apitoken as deleted")
}
InvalidateRabbitMQAuthContextsForEventStepInstance(eventStepInstance.ID)
if eventStepInstance.ContinueOnError {
processEventFinishAndNextStepStart(EventNotification{
EventStepInstanceID: eventStepInstance.ID,
+4
View File
@@ -23,12 +23,14 @@ type RPCQueueStruct struct {
Queue string
RoutingKey string
Handler RPCQueueHandler
Scopes []string
}
type DirectQueueStruct struct {
Exchange string
Queue string
RoutingKey string
Handler QueueHandler
Scopes []string
}
type channelMutex struct {
@@ -64,11 +66,13 @@ func (r *rabbitMQConnection) AddRPCQueue(input RPCQueueStruct) {
r.addListenerMutex.Lock()
r.RPCQueues = append(r.RPCQueues, input)
r.addListenerMutex.Unlock()
RegisterRabbitMQRPCScopePolicy(input.Queue, input.Scopes)
}
func (r *rabbitMQConnection) AddDirectQueue(input DirectQueueStruct) {
r.addListenerMutex.Lock()
r.DirectQueues = append(r.DirectQueues, input)
r.addListenerMutex.Unlock()
RegisterRabbitMQRPCScopePolicy(input.Queue, input.Scopes)
}
func (r *rabbitMQConnection) startListeners() {
exclusiveQueue := true
+7 -3
View File
@@ -9,6 +9,7 @@ import (
"strings"
"time"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -89,6 +90,7 @@ func init() {
Queue: "mythic_consume_c2_sync",
RoutingKey: C2_SYNC_ROUTING_KEY,
Handler: processC2SyncMessages,
Scopes: []string{},
})
}
@@ -192,7 +194,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, false)
go ResolveAllOperationsMessageBySource(getDownContainerSource(c2Profile.Name), 0)
go autoStartC2Profile(c2Profile, false)
go autoStartC2Profile(c2Profile, false, RabbitMQAuthContext{})
if newProfile {
go reSyncPayloadTypes()
}
@@ -362,7 +364,7 @@ func updateC2Parameters(in C2SyncMessage, c2Profile databaseStructs.C2profile) e
return nil
}
func autoStartC2Profile(c2Profile databaseStructs.C2profile, startC2Only bool) *C2StartServerMessageResponse {
func autoStartC2Profile(c2Profile databaseStructs.C2profile, startC2Only bool, authContext RabbitMQAuthContext) *C2StartServerMessageResponse {
// on a new sync, if it's not p2p, ask it to start
var c2StartResp *C2StartServerMessageResponse
c2StartResp = new(C2StartServerMessageResponse)
@@ -372,7 +374,7 @@ func autoStartC2Profile(c2Profile databaseStructs.C2profile, startC2Only bool) *
CreateGraphQLSpectatorAPITokenAndSendOnStartMessage(c2Profile.Name)
}
if !c2Profile.IsP2p {
c2StartResp, err = RabbitMQConnection.SendC2RPCStartServer(C2StartServerMessage{Name: c2Profile.Name})
c2StartResp, err = RabbitMQConnection.SendC2RPCStartServer(C2StartServerMessage{Name: c2Profile.Name}, authContext)
time.Sleep(10 * time.Second) // give the server a chance to start back up
if err != nil {
logging.LogError(err, "Failed to send start message to C2 profile")
@@ -416,6 +418,8 @@ func autoReHostFiles(c2Profile databaseStructs.C2profile) {
FileUUID: newTagMap["agent_file_id"].(string),
HostURL: newTagMap["host_url"].(string),
Remove: false,
}, RabbitMQAuthContext{
SourceScopes: []string{mythicjwt.SCOPE_FILE_READ},
})
logging.LogInfo("got response from sending host file", "c2", newTagMap["c2_profile"].(string), "url", newTagMap["host_url"].(string))
// sleep for 3 seconds to allow the internal c2 binary to start up before we send anything else
@@ -46,6 +46,7 @@ func init() {
Queue: "mythic_consume_consuming_container_sync",
RoutingKey: CONSUMING_CONTAINER_SYNC_ROUTING_KEY,
Handler: processConsumingContainerSyncMessages,
Scopes: []string{},
})
}
@@ -3,6 +3,7 @@ package rabbitmq
import (
"encoding/json"
"fmt"
"github.com/its-a-feature/Mythic/database"
"github.com/its-a-feature/Mythic/logging"
amqp "github.com/rabbitmq/amqp091-go"
@@ -20,6 +21,7 @@ func init() {
Queue: CONTAINER_ON_START_RESPONSE,
RoutingKey: CONTAINER_ON_START_RESPONSE,
Handler: processContainerOnStartMessageResponse,
Scopes: []string{},
})
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
"github.com/its-a-feature/Mythic/logging"
amqp "github.com/rabbitmq/amqp091-go"
@@ -17,6 +18,7 @@ func init() {
Queue: CUSTOMBROWSER_EXPORT_FUNCTION_RESPONSE,
RoutingKey: CUSTOMBROWSER_EXPORT_FUNCTION_RESPONSE,
Handler: processCbExportFunctionResponseMessages,
Scopes: []string{mythicjwt.SCOPE_BROWSER_READ},
})
}
@@ -74,6 +74,7 @@ func init() {
Queue: CUSTOMBROWSER_SYNC_ROUTING_KEY,
RoutingKey: CUSTOMBROWSER_SYNC_ROUTING_KEY,
Handler: processCustomBrowserSyncMessages,
Scopes: []string{},
})
}
@@ -3,6 +3,7 @@ package rabbitmq
import (
"encoding/json"
"fmt"
"github.com/its-a-feature/Mythic/database"
"github.com/its-a-feature/Mythic/eventing"
"github.com/its-a-feature/Mythic/logging"
@@ -24,6 +25,7 @@ func init() {
Queue: EVENTING_CONDITIONAL_CHECK_RESPONSE,
RoutingKey: EVENTING_CONDITIONAL_CHECK_RESPONSE,
Handler: processEventingConditionalCheckResponse,
Scopes: []string{},
})
}
@@ -3,6 +3,7 @@ package rabbitmq
import (
"encoding/json"
"fmt"
"github.com/its-a-feature/Mythic/database"
"github.com/its-a-feature/Mythic/eventing"
"github.com/its-a-feature/Mythic/logging"
@@ -23,6 +24,7 @@ func init() {
Queue: EVENTING_CUSTOM_FUNCTION_RESPONSE,
RoutingKey: EVENTING_CUSTOM_FUNCTION_RESPONSE,
Handler: processEventingCustomFunctionResponse,
Scopes: []string{},
})
}
@@ -3,11 +3,12 @@ package rabbitmq
import (
"encoding/json"
"fmt"
"time"
"github.com/its-a-feature/Mythic/database"
"github.com/its-a-feature/Mythic/eventing"
"github.com/its-a-feature/Mythic/logging"
amqp "github.com/rabbitmq/amqp091-go"
"time"
)
type ResponseInterceptMessageResponse struct {
@@ -26,6 +27,7 @@ func init() {
Queue: EVENTING_RESPONSE_INTERCEPT_RESPONSE,
RoutingKey: EVENTING_RESPONSE_INTERCEPT_RESPONSE,
Handler: processEventingResponseInterceptResponse,
Scopes: []string{},
})
}
@@ -3,12 +3,13 @@ package rabbitmq
import (
"encoding/json"
"fmt"
"time"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/eventing"
"github.com/its-a-feature/Mythic/logging"
amqp "github.com/rabbitmq/amqp091-go"
"time"
)
type TaskInterceptMessageResponse struct {
@@ -29,6 +30,7 @@ func init() {
Queue: EVENTING_TASK_INTERCEPT_RESPONSE,
RoutingKey: EVENTING_TASK_INTERCEPT_RESPONSE,
Handler: processEventingTaskInterceptResponse,
Scopes: []string{},
})
}
@@ -24,11 +24,11 @@ func init() {
Queue: MYTHIC_RPC_AGENTSTORAGE_CREATE,
RoutingKey: MYTHIC_RPC_AGENTSTORAGE_CREATE,
Handler: processMythicRPCAgentstorageCreate,
Scopes: []string{},
})
}
// Endpoint: MYTHIC_RPC_AGENTSTORAGE_CREATE
//
func MythicRPCAgentstorageCreate(input MythicRPCAgentstorageCreateMessage) MythicRPCAgentstorageCreateMessageResponse {
response := MythicRPCAgentstorageCreateMessageResponse{
Success: false,
@@ -23,11 +23,11 @@ func init() {
Queue: MYTHIC_RPC_AGENTSTORAGE_REMOVE,
RoutingKey: MYTHIC_RPC_AGENTSTORAGE_REMOVE,
Handler: processMythicRPCAgentstorageRemove,
Scopes: []string{},
})
}
// Endpoint: MYTHIC_RPC_AGENTSTORAGE_REMOVE
//
func MythicRPCAgentstorageRemove(input MythicRPCAgentstorageRemoveMessage) MythicRPCAgentstorageRemoveMessageResponse {
response := MythicRPCAgentstorageRemoveMessageResponse{
Success: false,
@@ -30,6 +30,7 @@ func init() {
Queue: MYTHIC_RPC_AGENTSTORAGE_SEARCH,
RoutingKey: MYTHIC_RPC_AGENTSTORAGE_SEARCH,
Handler: processMythicRPCAgentstorageSearch,
Scopes: []string{},
})
}
@@ -15,7 +15,6 @@ type MythicRPCAPITokenCreateMessage struct {
AgentTaskID *string `json:"agent_task_id"`
AgentCallbackID *string `json:"agent_callback_id"`
PayloadUUID *string `json:"payload_uuid"`
APITokenID *int `json:"apitoken_id"`
Scopes []string `json:"scopes"`
}
@@ -32,28 +31,11 @@ func init() {
Queue: MYTHIC_RPC_APITOKEN_CREATE, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_APITOKEN_CREATE, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCAPITokenCreate, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_APITOKEN_WRITE},
})
}
var baseScopes = []string{
mythicjwt.SCOPE_CALLBACK_WRITE,
mythicjwt.SCOPE_CREDENTIAL_WRITE,
mythicjwt.SCOPE_FILE_WRITE,
mythicjwt.SCOPE_PAYLOAD_WRITE,
mythicjwt.SCOPE_RESPONSE_WRITE,
mythicjwt.SCOPE_TAG_WRITE,
mythicjwt.SCOPE_TASK_WRITE,
}
func getAPITokenScopes(apitokenID int) ([]string, error) {
apiToken := databaseStructs.Apitokens{}
if err := database.DB.Get(&apiToken, `SELECT scopes FROM apitokens WHERE id=$1`, apitokenID); err != nil {
return nil, err
}
return []string(apiToken.Scopes), nil
}
func MythicRPCAPITokenCreate(input MythicRPCAPITokenCreateMessage) MythicRPCAPITokenCreateMessageResponse {
func MythicRPCAPITokenCreate(input MythicRPCAPITokenCreateMessage, authContext RabbitMQAuthContext) MythicRPCAPITokenCreateMessageResponse {
response := MythicRPCAPITokenCreateMessageResponse{
Success: false,
}
@@ -67,11 +49,19 @@ func MythicRPCAPITokenCreate(input MythicRPCAPITokenCreateMessage) MythicRPCAPIT
TokenValue: "",
Active: true,
Scopes: normalizedScopes,
OperatorID: authContext.OperatorID,
CreatedBy: authContext.OperatorID,
}
if authContext.EventStepInstanceID > 0 {
apiToken.EventStepInstanceID.Valid = true
apiToken.EventStepInstanceID.Int64 = int64(authContext.EventStepInstanceID)
}
if input.AgentTaskID != nil {
task := databaseStructs.Task{AgentTaskID: *input.AgentTaskID}
err = database.DB.Get(&task, `SELECT id, operator_id, operation_id, display_id, completed
FROM task WHERE agent_task_id=$1`, task.AgentTaskID)
err = database.DB.Get(&task, `SELECT
id, operator_id, operation_id, display_id, completed
FROM task
WHERE agent_task_id=$1 AND operation_id=$2`, task.AgentTaskID, authContext.OperationID)
if err != nil {
response.Error = err.Error()
return response
@@ -81,15 +71,15 @@ func MythicRPCAPITokenCreate(input MythicRPCAPITokenCreateMessage) MythicRPCAPIT
return response
}
apiToken.TokenType = mythicjwt.AUTH_METHOD_TASK
apiToken.OperatorID = task.OperatorID
apiToken.CreatedBy = task.OperatorID
apiToken.TaskID.Valid = true
apiToken.TaskID.Int64 = int64(task.ID)
apiToken.Name = fmt.Sprintf("Generated Task API Token via MythicRPC for Task %d", task.DisplayID)
} else if input.PayloadUUID != nil {
payload := databaseStructs.Payload{UuID: *input.PayloadUUID}
err = database.DB.Get(&payload, `SELECT id, operator_id, operation_id, deleted, build_phase
FROM payload WHERE uuid=$1`, payload.UuID)
err = database.DB.Get(&payload, `SELECT
id, operator_id, operation_id, deleted, build_phase
FROM payload
WHERE uuid=$1 AND operation_id=$2`, payload.UuID, authContext.OperationID)
if err != nil {
response.Error = err.Error()
return response
@@ -99,45 +89,20 @@ func MythicRPCAPITokenCreate(input MythicRPCAPITokenCreateMessage) MythicRPCAPIT
return response
}
apiToken.TokenType = mythicjwt.AUTH_METHOD_PAYLOAD
apiToken.OperatorID = payload.OperatorID
apiToken.CreatedBy = payload.OperatorID
apiToken.PayloadID.Valid = true
apiToken.PayloadID.Int64 = int64(payload.ID)
apiToken.Name = fmt.Sprintf("Generated Payload API Token via MythicRPC for Payload %s", payload.UuID)
} else if input.AgentCallbackID != nil {
callback := databaseStructs.Callback{}
callback.AgentCallbackID = *input.AgentCallbackID
err = database.DB.Get(&callback, `SELECT operation_id, id, display_id FROM callback WHERE agent_callback_id=$1`, callback.AgentCallbackID)
err = database.DB.Get(&callback, `SELECT
operation_id, id, display_id
FROM callback
WHERE agent_callback_id=$1 AND operation_id=$2`, callback.AgentCallbackID, authContext.OperationID)
if err != nil {
response.Error = err.Error()
return response
}
operatorOperationData := []databaseStructs.Operatoroperation{}
err = database.DB.Select(&operatorOperationData, `SELECT
operator.account_type "operator.account_type",
operator.id "operator.id",
operator.deleted "operator.deleted",
operator.active "operator.active"
FROM operatoroperation
JOIN operator ON operatoroperation.operator_id = operator.id
WHERE operatoroperation.operation_id=$1`, callback.OperationID)
if err != nil {
response.Error = err.Error()
return response
}
for i, _ := range operatorOperationData {
if operatorOperationData[i].CurrentOperator.AccountType == databaseStructs.AccountTypeBot {
if !operatorOperationData[i].CurrentOperator.Deleted && operatorOperationData[i].CurrentOperator.Active {
apiToken.OperatorID = operatorOperationData[i].CurrentOperator.ID
apiToken.CreatedBy = operatorOperationData[i].CurrentOperator.ID
apiToken.Name = fmt.Sprintf("Generated Callback API Token via MythicRPC for Callback %d", callback.DisplayID)
}
}
}
if apiToken.OperatorID == 0 {
response.Error = "Need a bot account assigned to this operation that's active and not deleted"
return response
}
apiToken.TokenType = mythicjwt.AUTH_METHOD_CALLBACK
apiToken.CallbackID.Valid = true
apiToken.CallbackID.Int64 = int64(callback.ID)
@@ -145,13 +110,10 @@ func MythicRPCAPITokenCreate(input MythicRPCAPITokenCreateMessage) MythicRPCAPIT
response.Error = "No task or callback information provided, can't generate apitoken"
return response
}
defaultScopes := baseScopes
if input.APITokenID != nil && *input.APITokenID > 0 {
defaultScopes, err = getAPITokenScopes(*input.APITokenID)
if err != nil {
response.Error = err.Error()
return response
}
defaultScopes := authContext.SourceScopes
if len(defaultScopes) == 0 {
response.Error = "missing RabbitMQ auth context source scopes"
return response
}
err = mythicjwt.CanGrantAPITokenScopes(defaultScopes, normalizedScopes)
if err != nil {
@@ -159,9 +121,9 @@ func MythicRPCAPITokenCreate(input MythicRPCAPITokenCreateMessage) MythicRPCAPIT
return response
}
statement, err := database.DB.PrepareNamed(`INSERT INTO apitokens
(token_value, operator_id, token_type, active, "name", created_by, task_id, callback_id, payload_id, scopes)
(token_value, operator_id, token_type, active, "name", created_by, task_id, callback_id, payload_id, scopes, eventstepinstance_id)
VALUES
(:token_value, :operator_id, :token_type, :active, :name, :created_by, :task_id, :callback_id, :payload_id, :scopes)
(:token_value, :operator_id, :token_type, :active, :name, :created_by, :task_id, :callback_id, :payload_id, :scopes, :eventstepinstance_id)
RETURNING id`)
if err != nil {
response.Error = err.Error()
@@ -204,5 +166,10 @@ func processMythicRPCAPITokenCreate(msg amqp.Delivery) interface{} {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCAPITokenCreate(incomingMessage)
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCAPITokenCreate(incomingMessage, authContext)
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"strings"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -27,11 +28,12 @@ func init() {
Queue: MYTHIC_RPC_ARTIFACT_CREATE,
RoutingKey: MYTHIC_RPC_ARTIFACT_CREATE,
Handler: processMythicRPCArtifactCreate,
Scopes: []string{mythicjwt.SCOPE_RESPONSE_WRITE},
})
}
// Endpoint: MYTHIC_RPC_ARTIFACT_CREATE
func MythicRPCArtifactCreate(input MythicRPCArtifactCreateMessage) MythicRPCArtifactCreateMessageResponse {
func MythicRPCArtifactCreate(input MythicRPCArtifactCreateMessage, authContext RabbitMQAuthContext) MythicRPCArtifactCreateMessageResponse {
response := MythicRPCArtifactCreateMessageResponse{
Success: false,
}
@@ -41,12 +43,12 @@ func MythicRPCArtifactCreate(input MythicRPCArtifactCreateMessage) MythicRPCArti
taskArtifact.Artifact = []byte(input.ArtifactMessage)
taskArtifact.BaseArtifact = input.BaseArtifactType
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
task.operation_id,
err := database.DB.Get(&task, `SELECT
callback.host "callback.host"
FROM task
JOIN callback ON task.callback_id = callback.id
WHERE task.id=$1`, input.TaskID); err != nil {
WHERE task.id=$1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
response.Error = err.Error()
return response
}
@@ -55,34 +57,49 @@ func MythicRPCArtifactCreate(input MythicRPCArtifactCreateMessage) MythicRPCArti
} else {
taskArtifact.Host = task.Callback.Host
}
taskArtifact.OperationID = task.OperationID
if statement, err := database.DB.PrepareNamed(`INSERT INTO taskartifact
(task_id, artifact, base_artifact, host, operation_id)
VALUES (:task_id, :artifact, :base_artifact, :host, :operation_id)
RETURNING id`); err != nil {
taskArtifact.OperationID = authContext.OperationID
if authContext.APITokensID > 0 {
taskArtifact.APITokensID.Valid = true
taskArtifact.APITokensID.Int64 = int64(authContext.APITokensID)
}
if authContext.EventStepInstanceID > 0 {
taskArtifact.EventStepInstanceID.Valid = true
taskArtifact.EventStepInstanceID.Int64 = int64(authContext.EventStepInstanceID)
}
statement, err := database.DB.PrepareNamed(`INSERT INTO taskartifact
(task_id, artifact, base_artifact, host, operation_id, apitokens_id, eventstepinstance_id)
VALUES (:task_id, :artifact, :base_artifact, :host, :operation_id, :apitokens_id, :eventstepinstance_id)
RETURNING id`)
if err != nil {
logging.LogError(err, "Failed to save taskartifact data to database")
response.Error = err.Error()
return response
} else if err = statement.Get(&taskArtifact.ID, taskArtifact); err != nil {
logging.LogError(err, "Failed to save taskartifact data to database")
response.Error = err.Error()
return response
} else {
response.Success = true
go emitArtifactLog(taskArtifact.ID)
return response
}
err = statement.Get(&taskArtifact.ID, taskArtifact)
if err != nil {
logging.LogError(err, "Failed to save taskartifact data to database")
response.Error = err.Error()
return response
}
response.Success = true
go emitArtifactLog(taskArtifact.ID)
return response
}
func processMythicRPCArtifactCreate(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCArtifactCreateMessage{}
responseMsg := MythicRPCArtifactCreateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCArtifactCreate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCArtifactCreate(incomingMessage, authContext)
}
@@ -3,6 +3,8 @@ package rabbitmq
import (
"encoding/json"
"fmt"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/mitchellh/mapstructure"
"github.com/its-a-feature/Mythic/database"
@@ -12,7 +14,6 @@ import (
)
type MythicRPCArtifactSearchMessage struct {
TaskID int `json:"task_id"` //required
SearchArtifacts MythicRPCArtifactSearchArtifactData `json:"artifact"`
}
type MythicRPCArtifactSearchMessageResponse struct {
@@ -33,78 +34,72 @@ func init() {
Queue: MYTHIC_RPC_ARTIFACT_SEARCH,
RoutingKey: MYTHIC_RPC_ARTIFACT_SEARCH,
Handler: processMythicRPCArtifactSearch,
Scopes: []string{mythicjwt.SCOPE_RESPONSE_READ},
})
}
// Endpoint: MYTHIC_RPC_PROCESS_SEARCH
func MythicRPCArtifactSearch(input MythicRPCArtifactSearchMessage) MythicRPCArtifactSearchMessageResponse {
func MythicRPCArtifactSearch(input MythicRPCArtifactSearchMessage, authContext RabbitMQAuthContext) MythicRPCArtifactSearchMessageResponse {
response := MythicRPCArtifactSearchMessageResponse{
Success: false,
Artifacts: []MythicRPCArtifactSearchArtifactData{},
}
paramDict := make(map[string]interface{})
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
task.id,
callback.operation_id "callback.operation_id"
FROM task
JOIN callback ON task.callback_id = callback.id
WHERE task.id=$1`, input.TaskID); err != nil {
logging.LogError(err, "Failed to search for task information when searching for artifacts")
paramDict["operation_id"] = authContext.OperationID
searchString := `SELECT * FROM taskartifact WHERE operation_id=:operation_id `
if input.SearchArtifacts.Host != nil {
paramDict["host"] = fmt.Sprintf("%%%s%%", *input.SearchArtifacts.Host)
searchString += "AND host ILIKE :host "
}
if input.SearchArtifacts.ArtifactMessage != nil {
paramDict["artifact"] = fmt.Sprintf("%%%s%%", *input.SearchArtifacts.ArtifactMessage)
searchString += "AND artifact LIKE :artifact "
}
if input.SearchArtifacts.TaskID != nil {
paramDict["task_id"] = *input.SearchArtifacts.TaskID
searchString += "AND task_id=:task_id "
}
if input.SearchArtifacts.ArtifactType != nil {
paramDict["base_artifact"] = fmt.Sprintf("%%%s%%", *input.SearchArtifacts.ArtifactType)
searchString += "AND base_artifact LIKE :base_artifact "
}
searchString += " ORDER BY task_id DESC"
rows, err := database.DB.NamedQuery(searchString, paramDict)
if err != nil {
logging.LogError(err, "Failed to search artifact information")
response.Error = err.Error()
return response
} else {
paramDict["operation_id"] = task.Callback.OperationID
searchString := `SELECT * FROM taskartifact WHERE operation_id=:operation_id `
if input.SearchArtifacts.Host != nil {
paramDict["host"] = fmt.Sprintf("%%%s%%", *input.SearchArtifacts.Host)
searchString += "AND host ILIKE :host "
}
if input.SearchArtifacts.ArtifactMessage != nil {
paramDict["artifact"] = fmt.Sprintf("%%%s%%", *input.SearchArtifacts.ArtifactMessage)
searchString += "AND artifact LIKE :artifact "
}
if input.SearchArtifacts.TaskID != nil {
paramDict["task_id"] = *input.SearchArtifacts.TaskID
searchString += "AND task_id=:task_id "
}
if input.SearchArtifacts.ArtifactType != nil {
paramDict["base_artifact"] = fmt.Sprintf("%%%s%%", *input.SearchArtifacts.ArtifactType)
searchString += "AND base_artifact LIKE :base_artifact "
}
searchString += " ORDER BY task_id DESC"
if rows, err := database.DB.NamedQuery(searchString, paramDict); err != nil {
logging.LogError(err, "Failed to search artifact information")
response.Error = err.Error()
return response
}
defer rows.Close()
for rows.Next() {
result := MythicRPCArtifactSearchArtifactData{}
searchResult := databaseStructs.Taskartifact{}
if err = rows.StructScan(&searchResult); err != nil {
logging.LogError(err, "Failed to get row from artifacts for search")
} else if err = mapstructure.Decode(searchResult, &result); err != nil {
logging.LogError(err, "Failed to map artifact search results into array")
} else {
defer rows.Close()
for rows.Next() {
result := MythicRPCArtifactSearchArtifactData{}
searchResult := databaseStructs.Taskartifact{}
if err = rows.StructScan(&searchResult); err != nil {
logging.LogError(err, "Failed to get row from artifacts for search")
} else if err = mapstructure.Decode(searchResult, &result); err != nil {
logging.LogError(err, "Failed to map artifact search results into array")
} else {
response.Artifacts = append(response.Artifacts, result)
}
}
response.Success = true
return response
response.Artifacts = append(response.Artifacts, result)
}
}
response.Success = true
return response
}
func processMythicRPCArtifactSearch(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCArtifactSearchMessage{}
responseMsg := MythicRPCArtifactSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCArtifactSearch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCArtifactSearch(incomingMessage, authContext)
}
@@ -3,11 +3,13 @@ package rabbitmq
import (
"encoding/json"
"fmt"
"strings"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
amqp "github.com/rabbitmq/amqp091-go"
"strings"
)
type MythicRPCC2UpdateStatusMessage struct {
@@ -26,6 +28,7 @@ func init() {
Queue: MYTHIC_RPC_C2_UPDATE_STATUS,
RoutingKey: MYTHIC_RPC_C2_UPDATE_STATUS,
Handler: processMythicRPCC2UpdateStatus,
Scopes: []string{mythicjwt.SCOPE_C2_WRITE},
})
}
@@ -4,6 +4,8 @@ import (
"database/sql"
"encoding/json"
"errors"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -28,17 +30,17 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_ADD_COMMAND,
RoutingKey: MYTHIC_RPC_CALLBACK_ADD_COMMAND,
Handler: processMythicRPCCallbackAddCommand,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_WRITE},
})
}
// Endpoint: MYTHIC_RPC_CALLBACK_ADD_COMMAND
func MythicRPCCallbackAddCommand(input MythicRPCCallbackAddCommandMessage) MythicRPCCallbackAddCommandMessageResponse {
func MythicRPCCallbackAddCommand(input MythicRPCCallbackAddCommandMessage, authContext RabbitMQAuthContext) MythicRPCCallbackAddCommandMessageResponse {
response := MythicRPCCallbackAddCommandMessageResponse{
Success: false,
}
CallbackID := 0
PayloadTypeID := 0
OperatorID := 0
task := databaseStructs.Task{}
callback := databaseStructs.Callback{}
if input.TaskID > 0 {
@@ -49,7 +51,7 @@ func MythicRPCCallbackAddCommand(input MythicRPCCallbackAddCommandMessage) Mythi
FROM task
JOIN callback on task.callback_id = callback.id
JOIN payload on callback.registered_payload_id = payload.id
WHERE task.id=$1`, input.TaskID)
WHERE task.id=$1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to find task in MythicRPCCallbackAddCommand")
response.Error = err.Error()
@@ -57,14 +59,13 @@ func MythicRPCCallbackAddCommand(input MythicRPCCallbackAddCommandMessage) Mythi
}
CallbackID = task.Callback.ID
PayloadTypeID = task.Callback.Payload.PayloadTypeID
OperatorID = task.OperatorID
} else if input.AgentCallbackID != "" {
err := database.DB.Get(&callback, `SELECT
callback.id, callback.operator_id,
payload.payload_type_id "callback.payload.payload_type_id"
FROM callback
JOIN payload on callback.registered_payload_id = payload.id
WHERE callback.agent_callback_id = $1`, input.AgentCallbackID)
WHERE callback.agent_callback_id = $1 AND callback.operation_id=$2`, input.AgentCallbackID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to find task in MythicRPCCallbackAddCommand")
response.Error = err.Error()
@@ -72,7 +73,6 @@ func MythicRPCCallbackAddCommand(input MythicRPCCallbackAddCommandMessage) Mythi
}
CallbackID = callback.ID
PayloadTypeID = callback.Payload.PayloadTypeID
OperatorID = callback.OperatorID
}
if input.PayloadType != "" {
err := database.DB.Get(&PayloadTypeID, `SELECT id FROM payloadtype WHERE "name"=$1`, input.PayloadType)
@@ -82,70 +82,79 @@ func MythicRPCCallbackAddCommand(input MythicRPCCallbackAddCommandMessage) Mythi
return response
}
}
if CallbackID == 0 {
if len(input.CallbackIDs) == 0 && CallbackID == 0 {
response.Error = "No callback supplied"
return response
}
if len(input.CallbackIDs) > 0 {
for _, c := range input.CallbackIDs {
err := CallbackAddCommand(c, PayloadTypeID, OperatorID, input.Commands)
err := CallbackAddCommand(c, PayloadTypeID, authContext.OperatorID, input.Commands, authContext.APITokensID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to add commands to callback")
response.Error = err.Error()
return response
}
}
response.Success = true
return response
}
err := CallbackAddCommand(CallbackID, PayloadTypeID, OperatorID, input.Commands)
if err != nil {
logging.LogError(err, "Failed to add commands to callback")
response.Error = err.Error()
return response
if CallbackID != 0 {
err := CallbackAddCommand(CallbackID, PayloadTypeID, authContext.OperatorID, input.Commands, authContext.APITokensID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to add commands to callback")
response.Error = err.Error()
return response
}
}
response.Success = true
return response
}
func CallbackAddCommand(callbackID int, payloadtypeID int, operatorID int, commands []string) error {
func CallbackAddCommand(callbackID int, payloadtypeID int, operatorID int, commands []string, apitokensID int, operationID int) error {
for _, command := range commands {
logging.LogDebug("trying to add command", "cmd", command)
// first check if the command is already loaded
// if not, try to add it as a loaded command
databaseCommand := databaseStructs.Command{}
loadedCommand := databaseStructs.Loadedcommands{}
if err := database.DB.Get(&databaseCommand, `SELECT
err := database.DB.Get(&databaseCommand, `SELECT
id, "version"
FROM command
WHERE command.cmd=$1 AND command.payload_type_id=$2`,
command, payloadtypeID); err != nil {
command, payloadtypeID)
if err != nil {
logging.LogError(err, "Failed to find command to load")
return errors.New("Failed to find command: " + command)
} else if err := database.DB.Get(&loadedCommand, `SELECT id
}
err = database.DB.Get(&loadedCommand, `SELECT
loadedcommands.id
FROM loadedcommands
WHERE command_id=$1 AND callback_id=$2`,
databaseCommand.ID, callbackID); err == nil {
JOIN callback on callback.id = loadedcommands.callback_id
WHERE command_id=$1 AND callback_id=$2 AND callback.operation_id=$3`,
databaseCommand.ID, callbackID, operationID)
if err == nil {
continue
} else if err == sql.ErrNoRows {
}
if errors.Is(err, sql.ErrNoRows) {
// this never existed, so let's add it as a loaded command
loadedCommand.Version = databaseCommand.Version
loadedCommand.CallbackID = callbackID
loadedCommand.CommandID = databaseCommand.ID
loadedCommand.OperatorID = operatorID
if _, err := database.DB.NamedExec(`INSERT INTO loadedcommands
("version", callback_id, command_id, operator_id)
VALUES (:version, :callback_id, :command_id, :operator_id)`,
loadedCommand); err != nil {
if apitokensID > 0 {
loadedCommand.APITokensID.Valid = true
loadedCommand.APITokensID.Int64 = int64(apitokensID)
}
_, err = database.DB.NamedExec(`INSERT INTO loadedcommands
("version", callback_id, command_id, operator_id, apitokens_id)
VALUES (:version, :callback_id, :command_id, :operator_id, :apitokens_id)`,
loadedCommand)
if err != nil {
logging.LogError(err, "Failed to mark command as loaded in callback")
return err
} else {
continue
}
} else {
// we got some other sort of error
logging.LogError(err, "Failed to query database for loaded command")
return err
continue
}
// we got some other sort of error
logging.LogError(err, "Failed to query database for loaded command")
return err
}
return nil
}
@@ -154,11 +163,16 @@ func processMythicRPCCallbackAddCommand(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackAddCommandMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackAddCommand(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCallbackAddCommand(incomingMessage, authContext)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/utils"
"github.com/its-a-feature/Mythic/database"
@@ -45,18 +47,19 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_SEARCH_COMMAND, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_CALLBACK_SEARCH_COMMAND, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCCallbackSearchCommand, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_CALLBACK_READ},
})
}
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCCallbackSearchCommand(input MythicRPCCallbackSearchCommandMessage) MythicRPCCallbackSearchCommandMessageResponse {
func MythicRPCCallbackSearchCommand(input MythicRPCCallbackSearchCommandMessage, authContext RabbitMQAuthContext) MythicRPCCallbackSearchCommandMessageResponse {
response := MythicRPCCallbackSearchCommandMessageResponse{
Success: false,
}
foundCommands := []MythicRPCCommandSearchCommandData{}
loadedCommands := []databaseStructs.Loadedcommands{}
if input.CallbackID != nil {
if err := database.DB.Select(&loadedCommands, `SELECT
err := database.DB.Select(&loadedCommands, `SELECT
command.needs_admin "command.needs_admin",
command.help_cmd "command.help_cmd",
command.description "command.description",
@@ -70,18 +73,25 @@ func MythicRPCCallbackSearchCommand(input MythicRPCCallbackSearchCommandMessage)
FROM
loadedcommands
JOIN command on loadedcommands.command_id = command.id
WHERE loadedcommands.callback_id=$1`, input.CallbackID); err != nil {
JOIN callback on callback.id = loadedcommands.callback_id
WHERE loadedcommands.callback_id=$1 AND callback.operation_id=$2`, input.CallbackID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to search loaded commands for callback_id")
response.Error = err.Error()
return response
}
} else if input.TaskID != nil {
task := databaseStructs.Task{ID: *input.TaskID}
if err := database.DB.Get(&task.CallbackID, `SELECT callback_id FROM task WHERE id=$1`, task.ID); err != nil {
err := database.DB.Get(&task.CallbackID, `SELECT
callback_id
FROM task
WHERE id=$1 AND operation_id=$2`, task.ID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to find task number")
response.Error = err.Error()
return response
} else if err := database.DB.Select(&loadedCommands, `SELECT
}
err = database.DB.Select(&loadedCommands, `SELECT
command.needs_admin "command.needs_admin",
command.help_cmd "command.help_cmd",
command.description "command.description",
@@ -95,7 +105,8 @@ func MythicRPCCallbackSearchCommand(input MythicRPCCallbackSearchCommandMessage)
FROM
loadedcommands
JOIN command on loadedcommands.command_id = command.id
WHERE loadedcommands.callback_id=$1`, task.CallbackID); err != nil {
WHERE loadedcommands.callback_id=$1`, task.CallbackID)
if err != nil {
logging.LogError(err, "Failed to search loaded commands for callback_id")
response.Error = err.Error()
return response
@@ -181,11 +192,16 @@ func processMythicRPCCallbackSearchCommand(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackSearchCommandMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackSearchCommand(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCallbackSearchCommand(incomingMessage, authContext)
}
@@ -8,6 +8,7 @@ import (
"strings"
"github.com/google/uuid"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/eventing"
@@ -39,12 +40,11 @@ type MythicRPCCallbackCreateMessage struct {
ProcessName string `json:"process_name" mapstructure:"process_name"`
Cwd string `json:"cwd" mapstructure:"cwd"`
ImpersonationContext string `json:"impersonation_context" mapstructure:"impersonation_context"`
EventStepInstanceID *int `json:"eventstepinstance_id" mapstructure:"eventstepinstance_id"`
}
type MythicRPCCallbackCreateMessageResponse struct {
Success bool `json:"success"`
Error string `json:"error"`
CallbackUUID string `json:"callback_uuid"`
AgentCallbackID string `json:"agent_callback_id"`
CallbackID int `json:"callback_id"`
CallbackDisplayID int `json:"callback_display_id"`
}
@@ -55,11 +55,12 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_CREATE,
RoutingKey: MYTHIC_RPC_CALLBACK_CREATE,
Handler: processMythicRPCCallbackCreate,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_WRITE},
})
}
// Endpoint: MYTHIC_RPC_CALLBACK_CREATE
func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCallbackCreateMessageResponse {
func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage, authContext RabbitMQAuthContext) MythicRPCCallbackCreateMessageResponse {
response := MythicRPCCallbackCreateMessageResponse{
Success: false,
}
@@ -115,7 +116,7 @@ func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCall
JOIN operation ON payload.operation_id = operation.id
JOIN payloadtype ON payload.payload_type_id = payloadtype.id
JOIN operator ON payload.operator_id = operator.id
WHERE uuid=$1`, input.PayloadUUID)
WHERE uuid=$1 AND operation_id=$2`, input.PayloadUUID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to find payload when calling MythicRPCCallbackCreate")
response.Error = err.Error()
@@ -130,8 +131,11 @@ func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCall
return response
}
callback.RegisteredPayloadID = payload.ID
callback.OperationID = payload.Operation.ID
callback.OperatorID = payload.OperatorID
callback.OperationID = authContext.OperationID
callback.OperatorID = authContext.OperatorID
if callback.OperatorID == 0 {
callback.OperatorID = payload.OperatorID
}
if input.Description == "" {
callback.Description = payload.Description
} else {
@@ -152,9 +156,9 @@ func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCall
return response
}
defer transaction.Rollback()
if input.EventStepInstanceID != nil {
if authContext.EventStepInstanceID > 0 {
callback.EventStepInstanceID.Valid = true
callback.EventStepInstanceID.Int64 = int64(*input.EventStepInstanceID)
callback.EventStepInstanceID.Int64 = int64(authContext.EventStepInstanceID)
}
statement, err := transaction.PrepareNamed(`
INSERT INTO callback
@@ -192,10 +196,20 @@ func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCall
return response
}
for _, pc := range payloadCommands {
if _, err := database.DB.Exec(`INSERT INTO loadedcommands
(command_id, "version", callback_id, operator_id)
VALUES ($1, $2, $3, $4)`,
pc.CommandID, pc.Version, callback.ID, callback.OperatorID); err != nil {
loadedCommand := databaseStructs.Loadedcommands{
CommandID: pc.CommandID,
Version: pc.Version,
CallbackID: callback.ID,
OperatorID: callback.OperatorID,
}
if authContext.APITokensID > 0 {
loadedCommand.APITokensID.Valid = true
loadedCommand.APITokensID.Int64 = int64(authContext.APITokensID)
}
if _, err := database.DB.NamedExec(`INSERT INTO loadedcommands
(command_id, "version", callback_id, operator_id, apitokens_id)
VALUES (:command_id, :version, :callback_id, :operator_id, :apitokens_id)`,
loadedCommand); err != nil {
logging.LogError(err, "Failed to mark command as loaded into callback", "payloadcommand", pc)
}
}
@@ -214,17 +228,20 @@ func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCall
return response
}
for _, pc2p := range payloadC2Profiles {
if err := database.DB.Select(&payloadC2ProfileParameterInstances, `SELECT
err := database.DB.Select(&payloadC2ProfileParameterInstances, `SELECT
*
FROM c2profileparametersinstance
WHERE payload_id=$1 AND c2_profile_id=$2
`, payload.ID, pc2p.C2profile.ID); err != nil {
`, payload.ID, pc2p.C2profile.ID)
if err != nil {
logging.LogError(err, "Failed to get c2 profile parameter instances from payload in MythicRPCCallbackCreate")
response.Error = err.Error()
return response
} else if _, err := database.DB.Exec(`INSERT INTO callbackc2profiles
}
_, err = database.DB.Exec(`INSERT INTO callbackc2profiles
(callback_id, c2_profile_id)
VALUES ($1, $2)`, callback.ID, pc2p.C2profile.ID); err != nil {
VALUES ($1, $2)`, callback.ID, pc2p.C2profile.ID)
if err != nil {
logging.LogError(err, "Failed to associate c2 profile with callback")
response.Error = err.Error()
return response
@@ -237,16 +254,20 @@ func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCall
OperationID: callback.OperationID,
Metadata: "",
}
if _, err := database.DB.NamedExec(`INSERT INTO callbackgraphedge
(source_id, destination_id, c2_profile_id, operation_id, metadata)
VALUES (:source_id, :destination_id, :c2_profile_id, :operation_id, :metadata)`, edge); err != nil {
if authContext.APITokensID > 0 {
edge.APITokensID.Valid = true
edge.APITokensID.Int64 = int64(authContext.APITokensID)
}
_, err = database.DB.NamedExec(`INSERT INTO callbackgraphedge
(source_id, destination_id, c2_profile_id, operation_id, metadata, apitokens_id)
VALUES (:source_id, :destination_id, :c2_profile_id, :operation_id, :metadata, :apitokens_id)`, edge)
if err != nil {
logging.LogError(err, "Failed to create new callbackgraphedge for egress c2 profile in MythicRPCCallbackCreate")
response.Error = err.Error()
return response
} else {
logging.LogInfo("Created new callbackgraph edge", "c2", input.C2ProfileName, "callback", callback.ID)
callbackGraph.Add(callback, callback, pc2p.C2profile.Name, false)
}
logging.LogInfo("Created new callbackgraph edge", "c2", input.C2ProfileName, "callback", callback.ID)
callbackGraph.Add(callback, callback, pc2p.C2profile.Name, false)
}
for _, c2paraminstance := range payloadC2ProfileParameterInstances {
c2Instance := databaseStructs.C2profileparametersinstance{
@@ -305,27 +326,29 @@ func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCall
}
}
addCommandAugmentsToCallback(callback.ID, payload.Os, payload.Payloadtype.Name, callback.OperatorID)
addCommandAugmentsToCallback(callback.ID, payload.Os, payload.Payloadtype.Name, callback.OperatorID, authContext)
operationsMsg := fmt.Sprintf("New Callback (%d) %s@%s with pid %d", callback.DisplayID, callback.User, callback.Host, callback.PID)
go SendAllOperationsMessage(operationsMsg, callback.OperationID, "", database.MESSAGE_LEVEL_INFO, false)
// prep data to send for log messages and webhook messages
webhookData := NewCallbackWebhookData{
User: callback.User,
Host: callback.Host,
IPs: callback.IP,
Domain: callback.Domain,
ExternalIP: callback.ExternalIp,
ProcessName: callback.ProcessName,
PID: callback.PID,
Os: callback.Os,
Architecture: callback.Architecture,
AgentType: payload.Payloadtype.Name,
Description: callback.Description,
ExtraInfo: callback.ExtraInfo,
SleepInfo: callback.SleepInfo,
DisplayID: callback.DisplayID,
ID: callback.ID,
IntegrityLevel: callback.IntegrityLevel,
User: callback.User,
Host: callback.Host,
IPs: callback.IP,
Domain: callback.Domain,
ExternalIP: callback.ExternalIp,
ProcessName: callback.ProcessName,
PID: callback.PID,
Os: callback.Os,
Architecture: callback.Architecture,
AgentType: payload.Payloadtype.Name,
Description: callback.Description,
ExtraInfo: callback.ExtraInfo,
SleepInfo: callback.SleepInfo,
DisplayID: callback.DisplayID,
ID: callback.ID,
IntegrityLevel: callback.IntegrityLevel,
Cwd: callback.Cwd,
ImpersonationContext: callback.ImpersonationContext,
}
webhookMapData := map[string]interface{}{}
err = mapstructure.Decode(webhookData, &webhookMapData)
@@ -350,11 +373,11 @@ func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCall
callback.Host, payload.ID, callback.OperationID); err != nil {
logging.LogError(err, "Failed to register callback on host")
}
response.CallbackUUID = callback.AgentCallbackID
response.AgentCallbackID = callback.AgentCallbackID
response.CallbackID = callback.ID
response.CallbackDisplayID = callback.DisplayID
response.Success = true
go sendPtOnNewCallback(callback.ID)
go sendPtOnNewCallback(callback.ID, authContext)
go func(triggerData databaseStructs.Callback) {
EventingChannel <- EventNotification{
Trigger: eventing.TriggerCallbackNew,
@@ -364,14 +387,13 @@ func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCall
PayloadID: callback.RegisteredPayloadID,
}
}(callback)
return response
}
func sendPtOnNewCallback(callbackId int) {
func sendPtOnNewCallback(callbackId int, authContext RabbitMQAuthContext) {
allCallbackData := GetOnNewCallbackConfigurationForContainer(callbackId)
_ = RabbitMQConnection.SendPtOnNewCallback(allCallbackData)
_ = RabbitMQConnection.SendPtOnNewCallback(allCallbackData, authContext)
}
func addCommandAugmentsToCallback(callbackID int, payloadOS string, payloadType string, operatorID int) {
func addCommandAugmentsToCallback(callbackID int, payloadOS string, payloadType string, operatorID int, authContext RabbitMQAuthContext) {
commandAugmentPayloadTypes := []databaseStructs.Payloadtype{}
err := database.DB.Select(&commandAugmentPayloadTypes, `SELECT * FROM payloadtype
WHERE agent_type=$1 AND deleted=false`, "command_augment")
@@ -408,11 +430,20 @@ func addCommandAugmentsToCallback(callbackID int, payloadOS string, payloadType
// only add augment commands where the command is builtin or suggested
// this allows 'load' and 'unload' style commands to be created for more granular control
if builtin || suggested {
_, err = database.DB.Exec(`INSERT INTO loadedcommands
(command_id, callback_id, operator_id, version)
VALUES ($1, $2, $3, $4)
ON CONFLICT DO NOTHING`,
commandAugmentCommands[j].ID, callbackID, operatorID, commandAugmentCommands[j].Version)
loadedCommand := databaseStructs.Loadedcommands{
CommandID: commandAugmentCommands[j].ID,
CallbackID: callbackID,
OperatorID: operatorID,
Version: commandAugmentCommands[j].Version,
}
if authContext.APITokensID > 0 {
loadedCommand.APITokensID.Valid = true
loadedCommand.APITokensID.Int64 = int64(authContext.APITokensID)
}
_, err = database.DB.NamedExec(`INSERT INTO loadedcommands
(command_id, callback_id, operator_id, version, apitokens_id)
VALUES (:command_id, :callback_id, :operator_id, :version, :apitokens_id)
ON CONFLICT DO NOTHING`, loadedCommand)
if err != nil {
logging.LogError(err, "failed to add loaded command to callback")
}
@@ -425,11 +456,16 @@ func processMythicRPCCallbackCreate(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackCreateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackCreate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCallbackCreate(incomingMessage, authContext)
}
@@ -6,6 +6,7 @@ import (
"errors"
"github.com/google/uuid"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/logging"
amqp "github.com/rabbitmq/amqp091-go"
)
@@ -29,6 +30,7 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_DECRYPT_BYTES,
RoutingKey: MYTHIC_RPC_CALLBACK_DECRYPT_BYTES,
Handler: processMythicRPCCallbackDecryptBytes,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_WRITE},
})
}
@@ -38,14 +40,14 @@ func MythicRPCCallbackDecryptBytes(input MythicRPCCallbackDecryptBytesMessage) M
Success: false,
}
if cipherText, err := CallbackDecryptMessage(input); err != nil {
cipherText, err := CallbackDecryptMessage(input)
if err != nil {
response.Error = err.Error()
return response
} else {
response.Success = true
response.Message = cipherText
return response
}
response.Success = true
response.Message = cipherText
return response
}
func CallbackDecryptMessage(input MythicRPCCallbackDecryptBytesMessage) ([]byte, error) {
@@ -148,11 +150,11 @@ func processMythicRPCCallbackDecryptBytes(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackDecryptBytesMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackDecryptBytes(incomingMessage)
return responseMsg
}
return responseMsg
return MythicRPCCallbackDecryptBytes(incomingMessage)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
@@ -10,9 +12,7 @@ import (
)
type MythicRPCCallbackDisplayToRealIdSearchMessage struct {
CallbackDisplayID int `json:"callback_display_id"`
OperationName *string `json:"operation_name"`
OperationID *int `json:"operation_id"`
CallbackDisplayID int `json:"callback_display_id"`
}
// Every mythicRPC function call must return a response that includes the following two values
@@ -28,63 +28,47 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_DISPLAY_TO_REAL_ID_SEARCH, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_CALLBACK_DISPLAY_TO_REAL_ID_SEARCH, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCCallbackDisplayToRealIdSearch, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_CALLBACK_READ},
})
}
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCCallbackDisplayToRealIdSearch(input MythicRPCCallbackDisplayToRealIdSearchMessage) MythicRPCCallbackDisplayToRealIdSearchMessageResponse {
func MythicRPCCallbackDisplayToRealIdSearch(input MythicRPCCallbackDisplayToRealIdSearchMessage, authContext RabbitMQAuthContext) MythicRPCCallbackDisplayToRealIdSearchMessageResponse {
response := MythicRPCCallbackDisplayToRealIdSearchMessageResponse{
Success: false,
}
searchString := ""
if input.OperationName != nil {
searchString = `SELECT
callback.id
FROM
callback
JOIN operation on callback.operation_id = operation.id
WHERE callback.display_id=$1 AND operation.name=$2`
callback := databaseStructs.Callback{}
if err := database.DB.Get(&callback, searchString, input.CallbackDisplayID, *input.OperationName); err != nil {
logging.LogError(err, "Failed to find task based on task id and operation name")
response.Error = err.Error()
return response
} else {
response.CallbackID = callback.ID
response.Success = true
return response
}
} else if input.OperationID != nil {
searchString = `SELECT
callback.id
FROM
callback
WHERE callback.display_id=$1 AND callback.operation_id=$2`
callback := databaseStructs.Callback{}
if err := database.DB.Get(&callback, searchString, input.CallbackDisplayID, *input.OperationID); err != nil {
logging.LogError(err, "Failed to find task based on task id and operation id")
response.Error = err.Error()
return response
} else {
response.CallbackID = callback.ID
response.Success = true
return response
}
} else {
response.Error = "Must specify operation name or operation id"
searchString = `SELECT
callback.id
FROM
callback
WHERE callback.display_id=$1 AND callback.operation_id=$2`
callback := databaseStructs.Callback{}
err := database.DB.Get(&callback, searchString, input.CallbackDisplayID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to find task based on task id and operation id")
response.Error = err.Error()
return response
}
response.CallbackID = callback.ID
response.Success = true
return response
}
func processMythicRPCCallbackDisplayToRealIdSearch(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCCallbackDisplayToRealIdSearchMessage{}
responseMsg := MythicRPCCallbackDisplayToRealIdSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackDisplayToRealIdSearch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCallbackDisplayToRealIdSearch(incomingMessage, authContext)
}
@@ -3,6 +3,7 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/logging"
amqp "github.com/rabbitmq/amqp091-go"
)
@@ -23,6 +24,7 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_EDGE_REMOVE,
RoutingKey: MYTHIC_RPC_CALLBACK_EDGE_REMOVE,
Handler: processMythicRPCCallbackEdgeRemove,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_WRITE},
})
}
@@ -45,11 +47,11 @@ func processMythicRPCCallbackEdgeRemove(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackEdgeRemoveMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackEdgeRemove(incomingMessage)
return responseMsg
}
return responseMsg
return MythicRPCCallbackEdgeRemove(incomingMessage)
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"time"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -36,11 +37,12 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_EDGE_SEARCH,
RoutingKey: MYTHIC_RPC_CALLBACK_EDGE_SEARCH,
Handler: processMythicRPCCallbackEdgeSearch,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_READ},
})
}
// Endpoint: MYTHIC_RPC_CALLBACK_EDGE_SEARCH
func MythicRPCCallbackEdgeSearch(input MythicRPCCallbackEdgeSearchMessage) MythicRPCCallbackEdgeSearchMessageResponse {
func MythicRPCCallbackEdgeSearch(input MythicRPCCallbackEdgeSearchMessage, authContext RabbitMQAuthContext) MythicRPCCallbackEdgeSearchMessageResponse {
response := MythicRPCCallbackEdgeSearchMessageResponse{
Success: false,
Results: []MythicRPCCallbackEdgeSearchMessageResult{},
@@ -54,11 +56,12 @@ func MythicRPCCallbackEdgeSearch(input MythicRPCCallbackEdgeSearchMessage) Mythi
JOIN callback d on callbackgraphedge.destination_id = d.id
JOIN c2profile c2 on callbackgraphedge.c2_profile_id = c2.id
WHERE (s.agent_callback_id=:agent_callback_id OR s.id=:id OR
d.agent_callback_id=:agent_callback_id OR d.id=:id)`
d.agent_callback_id=:agent_callback_id OR d.id=:id) AND callbackgraphedge.operation_id=:operation_id`
rows, err := database.DB.NamedQuery(searchString, databaseStructs.Callback{
ID: input.AgentCallbackID,
AgentCallbackID: input.AgentCallbackUUID,
OperationID: authContext.OperationID,
})
if err != nil {
logging.LogError(err, "Failed to search callback edge information")
@@ -107,11 +110,16 @@ func processMythicRPCCallbackEdgeSearch(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackEdgeSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackEdgeSearch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCallbackEdgeSearch(incomingMessage, authContext)
}
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/logging"
amqp "github.com/rabbitmq/amqp091-go"
)
@@ -28,6 +29,7 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_ENCRYPT_BYTES,
RoutingKey: MYTHIC_RPC_CALLBACK_ENCRYPT_BYTES,
Handler: processMythicRPCCallbackEncryptBytes,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_WRITE},
})
}
@@ -36,14 +38,14 @@ func MythicRPCCallbackEncryptBytes(input MythicRPCCallbackEncryptBytesMessage) M
response := MythicRPCCallbackEncryptBytesMessageResponse{
Success: false,
}
if cipherText, err := CallbackEncryptMessage(input); err != nil {
cipherText, err := CallbackEncryptMessage(input)
if err != nil {
response.Error = err.Error()
return response
} else {
response.Success = true
response.Message = cipherText
return response
}
response.Success = true
response.Message = cipherText
return response
}
func CallbackEncryptMessage(input MythicRPCCallbackEncryptBytesMessage) ([]byte, error) {
cachedInfo, err := LookupEncryptionData(input.C2Profile, input.AgentCallbackID, false)
@@ -105,11 +107,11 @@ func processMythicRPCCallbackEncryptBytes(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackEncryptBytesMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackEncryptBytes(incomingMessage)
return responseMsg
}
return responseMsg
return MythicRPCCallbackEncryptBytes(incomingMessage)
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"time"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/logging"
amqp "github.com/rabbitmq/amqp091-go"
)
@@ -27,6 +28,7 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_NEXT_CHECKIN_RANGE, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_CALLBACK_NEXT_CHECKIN_RANGE, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCCallbackNextCheckinRange, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_CALLBACK_READ},
})
}
@@ -53,11 +55,11 @@ func processMythicRPCCallbackNextCheckinRange(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackNextCheckinRangeMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackNextCheckinRange(incomingMessage)
return responseMsg
}
return responseMsg
return MythicRPCCallbackNextCheckinRange(incomingMessage)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -26,11 +28,12 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_REMOVE_COMMAND,
RoutingKey: MYTHIC_RPC_CALLBACK_REMOVE_COMMAND,
Handler: processMythicRPCCallbackRemoveCommand,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_WRITE},
})
}
// Endpoint: MYTHIC_RPC_CALLBACK_REMOVE_COMMAND
func MythicRPCCallbackRemoveCommand(input MythicRPCCallbackRemoveCommandMessage) MythicRPCCallbackRemoveCommandMessageResponse {
func MythicRPCCallbackRemoveCommand(input MythicRPCCallbackRemoveCommandMessage, authContext RabbitMQAuthContext) MythicRPCCallbackRemoveCommandMessageResponse {
response := MythicRPCCallbackRemoveCommandMessageResponse{
Success: false,
}
@@ -45,7 +48,7 @@ func MythicRPCCallbackRemoveCommand(input MythicRPCCallbackRemoveCommandMessage)
FROM task
JOIN callback on task.callback_id = callback.id
JOIN payload on callback.registered_payload_id = payload.id
WHERE task.id=$1`, input.TaskID)
WHERE task.id=$1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to find task in MythicRPCCallbackAddCommand")
response.Error = err.Error()
@@ -59,7 +62,7 @@ func MythicRPCCallbackRemoveCommand(input MythicRPCCallbackRemoveCommandMessage)
payload.payload_type_id "callback.payload.payload_type_id"
FROM callback
JOIN payload on callback.registered_payload_id = payload.id
WHERE callback.agent_callback_id = $1`, input.AgentCallbackID)
WHERE callback.agent_callback_id = $1 AND callback.operation_id=$2`, input.AgentCallbackID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to find task in MythicRPCCallbackAddCommand")
response.Error = err.Error()
@@ -98,18 +101,14 @@ func MythicRPCCallbackRemoveCommand(input MythicRPCCallbackRemoveCommandMessage)
return response
}
}
response.Success = true
return response
}
if CallbackID == 0 {
response.Error = "No callback supplied"
return response
}
err := CallbackRemoveCommand(CallbackID, commandIDs)
if err != nil {
logging.LogError(err, "Failed to remove commands to callback")
response.Error = err.Error()
return response
if CallbackID != 0 {
err := CallbackRemoveCommand(CallbackID, commandIDs)
if err != nil {
logging.LogError(err, "Failed to remove commands to callback")
response.Error = err.Error()
return response
}
}
response.Success = true
return response
@@ -131,11 +130,16 @@ func processMythicRPCCallbackRemoveCommand(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackRemoveCommandMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackRemoveCommand(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCallbackRemoveCommand(incomingMessage, authContext)
}
@@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/mitchellh/mapstructure"
"github.com/its-a-feature/Mythic/database"
@@ -15,8 +16,6 @@ import (
)
type MythicRPCCallbackSearchMessage struct {
AgentCallbackUUID string `json:"agent_callback_id"`
CallbackID int `json:"callback_id"`
SearchCallbackID *int `json:"search_callback_id"`
SearchCallbackDisplayID *int `json:"search_callback_display_id"`
SearchCallbackUUID *string `json:"search_callback_uuid"`
@@ -78,27 +77,18 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_SEARCH,
RoutingKey: MYTHIC_RPC_CALLBACK_SEARCH,
Handler: processMythicRPCCallbackSearch,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_READ},
})
}
// Endpoint: MYTHIC_RPC_CALLBACK_SEARCH
func MythicRPCCallbackSearch(input MythicRPCCallbackSearchMessage) MythicRPCCallbackSearchMessageResponse {
func MythicRPCCallbackSearch(input MythicRPCCallbackSearchMessage, authContext RabbitMQAuthContext) MythicRPCCallbackSearchMessageResponse {
response := MythicRPCCallbackSearchMessageResponse{
Success: false,
}
searchResults := databaseStructs.Callback{}
callback := databaseStructs.Callback{}
err := database.DB.Get(&callback, `SELECT
operation_id
FROM callback
WHERE agent_callback_id=$1 OR id=$2`, input.AgentCallbackUUID, input.CallbackID)
if err != nil {
logging.LogError(err, "Failed to find callback UUID")
response.Error = err.Error()
return response
}
targetCallback := databaseStructs.Callback{
OperationID: callback.OperationID,
OperationID: authContext.OperationID,
}
if input.SearchCallbackID != nil {
targetCallback.ID = *input.SearchCallbackID
@@ -207,7 +197,6 @@ func MythicRPCCallbackSearch(input MythicRPCCallbackSearchMessage) MythicRPCCall
result.Timestamp = searchResults.Timestamp
result.LastCheckin = searchResults.LastCheckin
response.Results = append(response.Results, result)
}
response.Success = true
return response
@@ -217,11 +206,16 @@ func processMythicRPCCallbackSearch(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackSearch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCallbackSearch(incomingMessage, authContext)
}
@@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/utils"
"github.com/its-a-feature/Mythic/database"
@@ -52,11 +53,12 @@ func init() {
Queue: MYTHIC_RPC_CALLBACK_UPDATE,
RoutingKey: MYTHIC_RPC_CALLBACK_UPDATE,
Handler: processMythicRPCCallbackUpdate,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_WRITE},
})
}
// Endpoint: MYTHIC_RPC_CALLBACK_UPDATE
func MythicRPCCallbackUpdate(input MythicRPCCallbackUpdateMessage) MythicRPCCallbackUpdateMessageResponse {
func MythicRPCCallbackUpdate(input MythicRPCCallbackUpdateMessage, authContext RabbitMQAuthContext) MythicRPCCallbackUpdateMessageResponse {
response := MythicRPCCallbackUpdateMessageResponse{
Success: false,
}
@@ -65,7 +67,7 @@ func MythicRPCCallbackUpdate(input MythicRPCCallbackUpdateMessage) MythicRPCCall
if err := database.DB.Get(&callback, `SELECT
*
FROM callback
WHERE agent_callback_id=$1`, input.AgentCallbackID); err != nil {
WHERE agent_callback_id=$1 AND operation_id=$2`, input.AgentCallbackID, authContext.OperationID); err != nil {
logging.LogError(err, "Failed to find callback UUID")
response.Error = err.Error()
return response
@@ -74,7 +76,7 @@ func MythicRPCCallbackUpdate(input MythicRPCCallbackUpdateMessage) MythicRPCCall
if err := database.DB.Get(&callback, `SELECT
*
FROM callback
WHERE id=$1`, input.CallbackID); err != nil {
WHERE id=$1 AND operation_id=$2`, input.CallbackID, authContext.OperationID); err != nil {
logging.LogError(err, "Failed to find callback ID")
response.Error = err.Error()
return response
@@ -84,7 +86,7 @@ func MythicRPCCallbackUpdate(input MythicRPCCallbackUpdateMessage) MythicRPCCall
callback.*
FROM callback
JOIN task on task.callback_id = callback.id
WHERE task.id=$1`, *input.TaskID); err != nil {
WHERE task.id=$1 AND task.operation_id=$2`, *input.TaskID, authContext.OperationID); err != nil {
logging.LogError(err, "failed to get callback information from task")
response.Error = err.Error()
return response
@@ -207,11 +209,16 @@ func processMythicRPCCallbackUpdate(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackUpdateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackUpdate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCallbackUpdate(incomingMessage, authContext)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -24,11 +26,12 @@ func init() {
Queue: MYTHIC_RPC_CALLBACKTOKEN_CREATE,
RoutingKey: MYTHIC_RPC_CALLBACKTOKEN_CREATE,
Handler: processMythicRPCCallbackTokenCreate,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_WRITE},
})
}
// Endpoint: MYTHIC_RPC_CALLBACKTOKEN_CREATE
func MythicRPCCallbackTokenCreate(input MythicRPCCallbackTokenCreateMessage) MythicRPCCallbackTokenCreateMessageResponse {
func MythicRPCCallbackTokenCreate(input MythicRPCCallbackTokenCreateMessage, authContext RabbitMQAuthContext) MythicRPCCallbackTokenCreateMessageResponse {
response := MythicRPCCallbackTokenCreateMessageResponse{
Success: false,
}
@@ -40,7 +43,7 @@ func MythicRPCCallbackTokenCreate(input MythicRPCCallbackTokenCreateMessage) Myt
input.CallbackTokens[i].Action = "add"
}
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
err := database.DB.Get(&task, `SELECT
task.id, task.status, task.completed, task.status_timestamp_processed, task.operator_id, task.operation_id,
callback.host "callback.host",
callback.user "callback.user",
@@ -51,29 +54,36 @@ func MythicRPCCallbackTokenCreate(input MythicRPCCallbackTokenCreateMessage) Myt
FROM task
JOIN callback ON task.callback_id = callback.id
JOIN payload ON callback.registered_payload_id = payload.id
WHERE task.id = $1`, input.TaskID); err != nil {
WHERE task.id = $1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch task")
response.Error = err.Error()
return response
} else if err := handleAgentMessagePostResponseCallbackTokens(task, &input.CallbackTokens); err != nil {
}
err = handleAgentMessagePostResponseCallbackTokens(task, &input.CallbackTokens)
if err != nil {
logging.LogError(err, "Failed to create processes in MythicRPCProcessCreate")
response.Error = err.Error()
return response
} else {
response.Success = true
return response
}
response.Success = true
return response
}
func processMythicRPCCallbackTokenCreate(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCCallbackTokenCreateMessage{}
responseMsg := MythicRPCCallbackTokenCreateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackTokenCreate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCallbackTokenCreate(incomingMessage, authContext)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -24,11 +26,12 @@ func init() {
Queue: MYTHIC_RPC_CALLBACKTOKEN_REMOVE,
RoutingKey: MYTHIC_RPC_CALLBACKTOKEN_REMOVE,
Handler: processMythicRPCCallbackTokenRemove,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_WRITE},
})
}
// Endpoint: MYTHIC_RPC_CALLBACKTOKEN_CREATE
func MythicRPCCallbackTokenRemove(input MythicRPCCallbackTokenRemoveMessage) MythicRPCCallbackTokenRemoveMessageResponse {
func MythicRPCCallbackTokenRemove(input MythicRPCCallbackTokenRemoveMessage, authContext RabbitMQAuthContext) MythicRPCCallbackTokenRemoveMessageResponse {
response := MythicRPCCallbackTokenRemoveMessageResponse{
Success: false,
}
@@ -40,7 +43,7 @@ func MythicRPCCallbackTokenRemove(input MythicRPCCallbackTokenRemoveMessage) Myt
for i := 0; i < len(input.CallbackTokens); i++ {
input.CallbackTokens[i].Action = "remove"
}
if err := database.DB.Get(&task, `SELECT
err := database.DB.Get(&task, `SELECT
task.id, task.status, task.completed, task.status_timestamp_processed, task.operator_id, task.operation_id,
callback.host "callback.host",
callback.user "callback.user",
@@ -51,29 +54,36 @@ func MythicRPCCallbackTokenRemove(input MythicRPCCallbackTokenRemoveMessage) Myt
FROM task
JOIN callback ON task.callback_id = callback.id
JOIN payload ON callback.registered_payload_id = payload.id
WHERE task.id = $1`, input.TaskID); err != nil {
WHERE task.id = $1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch task")
response.Error = err.Error()
return response
} else if err := handleAgentMessagePostResponseCallbackTokens(task, &input.CallbackTokens); err != nil {
}
err = handleAgentMessagePostResponseCallbackTokens(task, &input.CallbackTokens)
if err != nil {
logging.LogError(err, "Failed to create processes in MythicRPCProcessCreate")
response.Error = err.Error()
return response
} else {
response.Success = true
return response
}
response.Success = true
return response
}
func processMythicRPCCallbackTokenRemove(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCCallbackTokenRemoveMessage{}
responseMsg := MythicRPCCallbackTokenRemoveMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackTokenRemove(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCallbackTokenRemove(incomingMessage, authContext)
}
@@ -3,6 +3,7 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -26,11 +27,12 @@ func init() {
Queue: MYTHIC_RPC_CALLBACKTOKEN_SEARCH,
RoutingKey: MYTHIC_RPC_CALLBACKTOKEN_SEARCH,
Handler: processMythicRPCCallbackTokenSearch,
Scopes: []string{mythicjwt.SCOPE_CALLBACK_READ},
})
}
// Endpoint: MYTHIC_RPC_CALLBACKTOKEN_CREATE
func MythicRPCCallbackTokenSearch(input MythicRPCCallbackTokenSearchMessage) MythicRPCCallbackTokenSearchMessageResponse {
func MythicRPCCallbackTokenSearch(input MythicRPCCallbackTokenSearchMessage, authContext RabbitMQAuthContext) MythicRPCCallbackTokenSearchMessageResponse {
response := MythicRPCCallbackTokenSearchMessageResponse{
Success: false,
}
@@ -40,7 +42,7 @@ func MythicRPCCallbackTokenSearch(input MythicRPCCallbackTokenSearchMessage) Myt
err := database.DB.Get(&task, `SELECT
task.id, task.callback_id
FROM task
WHERE task.id = $1`, input.TaskID)
WHERE task.id = $1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch task")
response.Error = err.Error()
@@ -48,13 +50,23 @@ func MythicRPCCallbackTokenSearch(input MythicRPCCallbackTokenSearchMessage) Myt
}
callbackID = task.CallbackID
} else if input.CallbackID != nil {
callbackID = *input.CallbackID
callback := databaseStructs.Callback{}
err := database.DB.Get(&callback, `SELECT
callback.id
FROM callback
WHERE callback.id = $1 AND callback.operation_id=$2`, *input.CallbackID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch callback")
response.Error = err.Error()
return response
}
callbackID = callback.ID
} else if input.AgentCallbackID != nil {
callback := databaseStructs.Callback{}
err := database.DB.Get(&callback, `SELECT
callback.id
FROM callback
WHERE callback.agent_callback_id = $1`, input.AgentCallbackID)
WHERE callback.agent_callback_id = $1 AND callback.operation_id=$2`, *input.AgentCallbackID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch callback")
response.Error = err.Error()
@@ -108,11 +120,16 @@ func processMythicRPCCallbackTokenSearch(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCallbackTokenSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCallbackTokenSearch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCallbackTokenSearch(incomingMessage, authContext)
}
@@ -3,9 +3,10 @@ package rabbitmq
import (
"encoding/json"
"fmt"
"github.com/its-a-feature/Mythic/utils"
"reflect"
"github.com/its-a-feature/Mythic/utils"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -47,6 +48,7 @@ func init() {
Queue: MYTHIC_RPC_COMMAND_SEARCH, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_COMMAND_SEARCH, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCCommandSearch, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{},
})
}
@@ -70,13 +72,14 @@ func MythicRPCCommandSearch(input MythicRPCCommandSearchMessage) MythicRPCComman
}
foundCommands := []MythicRPCCommandSearchCommandData{}
commands := []databaseStructs.Command{}
if err := database.DB.Select(&commands, `SELECT
err := database.DB.Select(&commands, `SELECT
command.*,
payloadtype.name "payloadtype.name"
FROM
command
JOIN payloadtype on command.payload_type_id = payloadtype.id
WHERE payloadtype.name=$1 AND command.deleted=false`, input.SearchPayloadTypeName); err != nil {
WHERE payloadtype.name=$1 AND command.deleted=false`, input.SearchPayloadTypeName)
if err != nil {
logging.LogError(err, "Failed to search for commands")
response.Error = err.Error()
return response
@@ -95,7 +98,8 @@ func MythicRPCCommandSearch(input MythicRPCCommandSearchMessage) MythicRPCComman
uiFeatures := command.SupportedUiFeatures.StructValue()
stringUIFeatures := interfaceArrayToStringArray(uiFeatures)
attributes := map[string]interface{}{}
if err := command.Attributes.Unmarshal(&attributes); err != nil {
err = command.Attributes.Unmarshal(&attributes)
if err != nil {
logging.LogError(err, "Failed to get attributes from command")
response.Error = "Failed to get attributes from command"
return response
@@ -198,11 +202,11 @@ func processMythicRPCCommandSearch(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCCommandSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCommandSearch(incomingMessage)
return responseMsg
}
return responseMsg
return MythicRPCCommandSearch(incomingMessage)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -24,11 +26,12 @@ func init() {
Queue: MYTHIC_RPC_CREDENTIAL_CREATE,
RoutingKey: MYTHIC_RPC_CREDENTIAL_CREATE,
Handler: processMythicRPCCredentialCreate,
Scopes: []string{mythicjwt.SCOPE_CREDENTIAL_WRITE},
})
}
// Endpoint: MYTHIC_RPC_CREDENTIAL_CREATE
func MythicRPCCredentialCreate(input MythicRPCCredentialCreateMessage) MythicRPCCredentialCreateMessageResponse {
func MythicRPCCredentialCreate(input MythicRPCCredentialCreateMessage, authContext RabbitMQAuthContext) MythicRPCCredentialCreateMessageResponse {
response := MythicRPCCredentialCreateMessageResponse{
Success: false,
}
@@ -37,8 +40,9 @@ func MythicRPCCredentialCreate(input MythicRPCCredentialCreateMessage) MythicRPC
return response
}
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
err := database.DB.Get(&task, `SELECT
task.id, task.status, task.completed, task.status_timestamp_processed, task.operator_id, task.operation_id,
task.apitokens_id, task.eventstepinstance_id,
callback.host "callback.host",
callback.user "callback.user",
callback.id "callback.id",
@@ -48,29 +52,36 @@ func MythicRPCCredentialCreate(input MythicRPCCredentialCreateMessage) MythicRPC
FROM task
JOIN callback ON task.callback_id = callback.id
JOIN payload ON callback.registered_payload_id = payload.id
WHERE task.id = $1`, input.TaskID); err != nil {
WHERE task.id = $1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch task")
response.Error = err.Error()
return response
} else if err := handleAgentMessagePostResponseCredentials(task, &input.Credentials); err != nil {
}
err = handleAgentMessagePostResponseCredentials(task, &input.Credentials)
if err != nil {
logging.LogError(err, "Failed to create credentials in MythicRPCCredentialCreate")
response.Error = err.Error()
return response
} else {
response.Success = true
return response
}
response.Success = true
return response
}
func processMythicRPCCredentialCreate(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCCredentialCreateMessage{}
responseMsg := MythicRPCCredentialCreateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCredentialCreate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCredentialCreate(incomingMessage, authContext)
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -11,7 +12,6 @@ import (
)
type MythicRPCCredentialSearchMessage struct {
TaskID int `json:"task_id"` //required
SearchCredentials MythicRPCCredentialSearchCredentialData `json:"credentials"`
}
type MythicRPCCredentialSearchMessageResponse struct {
@@ -34,86 +34,82 @@ func init() {
Queue: MYTHIC_RPC_CREDENTIAL_SEARCH,
RoutingKey: MYTHIC_RPC_CREDENTIAL_SEARCH,
Handler: processMythicRPCCredentialSearch,
Scopes: []string{mythicjwt.SCOPE_CREDENTIAL_READ},
})
}
// Endpoint: MYTHIC_RPC_CREDENTIAL_SEARCH
func MythicRPCCredentialSearch(input MythicRPCCredentialSearchMessage) MythicRPCCredentialSearchMessageResponse {
func MythicRPCCredentialSearch(input MythicRPCCredentialSearchMessage, authContext RabbitMQAuthContext) MythicRPCCredentialSearchMessageResponse {
response := MythicRPCCredentialSearchMessageResponse{
Success: false,
}
params := []interface{}{}
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
task.id,
task.operation_id
FROM task
WHERE task.id=$1`, input.TaskID); err != nil {
credentials := []databaseStructs.Credential{}
params = append(params, authContext.OperationID)
searchString := fmt.Sprintf(`SELECT * FROM credential WHERE operation_id=$%d `, len(params))
if input.SearchCredentials.Type != nil {
params = append(params, "%"+*input.SearchCredentials.Type+"%")
searchString += fmt.Sprintf("AND \"type\" ILIKE $%d ", len(params))
}
if input.SearchCredentials.Credential != nil {
params = append(params, "%"+*input.SearchCredentials.Credential+"%")
searchString += fmt.Sprintf("AND credential LIKE $%d ", len(params))
}
if input.SearchCredentials.Account != nil {
params = append(params, "%"+*input.SearchCredentials.Account+"%")
searchString += fmt.Sprintf("AND account ILIKE $%d ", len(params))
}
if input.SearchCredentials.Realm != nil {
params = append(params, "%"+*input.SearchCredentials.Realm+"%")
searchString += fmt.Sprintf("AND realm ILIKE $%d ", len(params))
}
if input.SearchCredentials.Comment != nil {
params = append(params, "%"+*input.SearchCredentials.Comment+"%")
searchString += fmt.Sprintf("AND comment ILIKE $%d ", len(params))
}
if input.SearchCredentials.Metadata != nil {
params = append(params, "%"+*input.SearchCredentials.Metadata+"%")
searchString += fmt.Sprintf("AND metadata ILIKE $%d ", len(params))
}
searchString += " ORDER BY id DESC"
err := database.DB.Select(&credentials, searchString, params...)
if err != nil {
logging.LogError(err, "Failed to search for credentials")
response.Error = err.Error()
return response
} else {
credentials := []databaseStructs.Credential{}
params = append(params, task.OperationID)
searchString := fmt.Sprintf(`SELECT * FROM credential WHERE operation_id=$%d `, len(params))
if input.SearchCredentials.Type != nil {
params = append(params, "%"+*input.SearchCredentials.Type+"%")
searchString += fmt.Sprintf("AND \"type\" ILIKE $%d ", len(params))
}
if input.SearchCredentials.Credential != nil {
params = append(params, "%"+*input.SearchCredentials.Credential+"%")
searchString += fmt.Sprintf("AND credential LIKE $%d ", len(params))
}
if input.SearchCredentials.Account != nil {
params = append(params, "%"+*input.SearchCredentials.Account+"%")
searchString += fmt.Sprintf("AND account ILIKE $%d ", len(params))
}
if input.SearchCredentials.Realm != nil {
params = append(params, "%"+*input.SearchCredentials.Realm+"%")
searchString += fmt.Sprintf("AND realm ILIKE $%d ", len(params))
}
if input.SearchCredentials.Comment != nil {
params = append(params, "%"+*input.SearchCredentials.Comment+"%")
searchString += fmt.Sprintf("AND comment ILIKE $%d ", len(params))
}
if input.SearchCredentials.Metadata != nil {
params = append(params, "%"+*input.SearchCredentials.Metadata+"%")
searchString += fmt.Sprintf("AND metadata ILIKE $%d ", len(params))
}
searchString += " ORDER BY id DESC"
if err := database.DB.Select(&credentials, searchString, params...); err != nil {
logging.LogError(err, "Failed to search for credentials")
response.Error = err.Error()
return response
} else {
returnedCredentials := []MythicRPCCredentialSearchCredentialData{}
if marshalledBytes, err := json.Marshal(credentials); err != nil {
logging.LogError(err, "Failed to marshal credential result")
response.Error = err.Error()
return response
} else if err := json.Unmarshal(marshalledBytes, &returnedCredentials); err != nil {
logging.LogError(err, "Failed to unmarshal credential results")
response.Error = err.Error()
return response
} else {
response.Success = true
response.Credentials = returnedCredentials
return response
}
}
}
returnedCredentials := []MythicRPCCredentialSearchCredentialData{}
marshalledBytes, err := json.Marshal(credentials)
if err != nil {
logging.LogError(err, "Failed to marshal credential result")
response.Error = err.Error()
return response
}
err = json.Unmarshal(marshalledBytes, &returnedCredentials)
if err != nil {
logging.LogError(err, "Failed to unmarshal credential results")
response.Error = err.Error()
return response
}
response.Success = true
response.Credentials = returnedCredentials
return response
}
func processMythicRPCCredentialSearch(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCCredentialSearchMessage{}
responseMsg := MythicRPCCredentialSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCCredentialSearch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCredentialSearch(incomingMessage, authContext)
}
@@ -5,6 +5,7 @@ import (
"fmt"
"strings"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -12,8 +13,6 @@ import (
)
type MythicRPCCustomBrowserSearchMessage struct {
TaskID *int `json:"task_id"`
OperationID *int `json:"operation_id"`
GetAllMatchingChildren bool `json:"all_matching_children"`
SearchCustomBrowser MythicRPCCustomBrowserSearchData `json:"custombrowser"`
}
@@ -44,36 +43,20 @@ type MythicRPCCustomBrowserSearchDataResponse struct {
func init() {
RabbitMQConnection.AddRPCQueue(RPCQueueStruct{
Exchange: MYTHIC_EXCHANGE,
Queue: CUSTOMBROWSER_SEARCH,
RoutingKey: CUSTOMBROWSER_SEARCH,
Queue: MYTHIC_RPC_CUSTOMBROWSER_SEARCH,
RoutingKey: MYTHIC_RPC_CUSTOMBROWSER_SEARCH,
Handler: processMythicRPCCustomBrowserSearch,
Scopes: []string{mythicjwt.SCOPE_BROWSER_READ},
})
}
func MythicRPCCustomBrowserSearch(input MythicRPCCustomBrowserSearchMessage) MythicRPCCustomBrowserSearchMessageResponse {
func MythicRPCCustomBrowserSearch(input MythicRPCCustomBrowserSearchMessage, authContext RabbitMQAuthContext) MythicRPCCustomBrowserSearchMessageResponse {
response := MythicRPCCustomBrowserSearchMessageResponse{
Success: false,
CustomBrowserEntries: []MythicRPCCustomBrowserSearchDataResponse{},
}
paramDict := make(map[string]interface{})
if input.TaskID != nil {
task := databaseStructs.Task{}
err := database.DB.Get(&task, `SELECT
task.id, task.operation_id
FROM task
WHERE task.id=$1`, *input.TaskID)
if err != nil {
response.Error = err.Error()
return response
}
paramDict["operation_id"] = task.OperationID
} else if input.OperationID != nil {
paramDict["operation_id"] = *input.OperationID
} else {
logging.LogError(nil, "must provide task_id or operation_id")
response.Error = "must provide task_id or operation_id"
return response
}
paramDict["operation_id"] = authContext.OperationID
paramDict["tree_type"] = input.SearchCustomBrowser.TreeType
searchString := `SELECT
mythictree.id, mythictree.task_id, mythictree.timestamp, mythictree.operation_id,
@@ -175,5 +158,10 @@ func processMythicRPCCustomBrowserSearch(msg amqp.Delivery) interface{} {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCustomBrowserSearch(incomingMessage)
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCCustomBrowserSearch(incomingMessage, authContext)
}
@@ -34,7 +34,7 @@ func init() {
})
}
func MythicRPCDirectFileTokenCreate(input MythicRPCDirectFileTokenCreateMessage) MythicRPCDirectFileTokenCreateMessageResponse {
func MythicRPCDirectFileTokenCreate(input MythicRPCDirectFileTokenCreateMessage, authContext RabbitMQAuthContext) MythicRPCDirectFileTokenCreateMessageResponse {
response := MythicRPCDirectFileTokenCreateMessageResponse{Success: false}
if input.FileUUID == "" {
response.Error = "file_uuid is required"
@@ -42,34 +42,26 @@ func MythicRPCDirectFileTokenCreate(input MythicRPCDirectFileTokenCreateMessage)
}
file := databaseStructs.Filemeta{}
err := database.DB.Get(&file, `SELECT
operator_id, operation_id, eventstepinstance_id, apitokens_id
id
FROM filemeta
WHERE agent_file_id=$1`,
input.FileUUID)
WHERE agent_file_id=$1 AND operation_id=$2`,
input.FileUUID, authContext.OperationID)
if err != nil {
response.Error = err.Error()
return response
}
user := databaseStructs.Operator{
ID: file.OperatorID,
ID: authContext.OperatorID,
CurrentOperationID: sql.NullInt64{
Valid: true,
Int64: int64(file.OperationID),
Int64: int64(authContext.OperationID),
},
}
eventstepInstanceID := 0
if file.EventStepInstanceID.Valid {
eventstepInstanceID = int(file.EventStepInstanceID.Int64)
}
scope := mythicjwt.SCOPE_FILE_READ
if input.Action == "upload" {
if input.Action == "upload" || input.Action == "both" {
scope = mythicjwt.SCOPE_FILE_WRITE
}
apitokenID := 0
if file.APITokensID.Valid {
apitokenID = int(file.APITokensID.Int64)
}
token, _, err := mythicjwt.GenerateScopedJWT(user, []string{scope}, input.FileUUID, directFileScopedTokenTTL, eventstepInstanceID, apitokenID)
token, _, err := mythicjwt.GenerateScopedJWT(user, []string{scope}, input.FileUUID, directFileScopedTokenTTL, authContext.EventStepInstanceID, authContext.APITokensID)
if err != nil {
response.Error = err.Error()
return response
@@ -82,11 +74,16 @@ func MythicRPCDirectFileTokenCreate(input MythicRPCDirectFileTokenCreateMessage)
func processMythicRPCDirectFileTokenCreate(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCDirectFileTokenCreateMessage{}
responseMsg := MythicRPCDirectFileTokenCreateMessageResponse{Success: false}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCDirectFileTokenCreate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCDirectFileTokenCreate(incomingMessage, authContext)
}
@@ -9,6 +9,7 @@ import (
"strings"
"github.com/google/uuid"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -20,8 +21,6 @@ type MythicRPCFileCreateMessage struct {
TaskID int `json:"task_id"`
PayloadUUID string `json:"payload_uuid"`
AgentCallbackID string `json:"agent_callback_id"`
OperationID int `json:"operation_id"`
OperatorID int `json:"operator_id"`
FileContents []byte `json:"file_contents"`
DeleteAfterFetch bool `json:"delete_after_fetch"`
Filename string `json:"filename"`
@@ -43,13 +42,14 @@ func init() {
Queue: MYTHIC_RPC_FILE_CREATE,
RoutingKey: MYTHIC_RPC_FILE_CREATE,
Handler: processMythicRPCFileCreate,
Scopes: []string{mythicjwt.SCOPE_FILE_WRITE},
})
}
// Endpoint: MYTHIC_RPC_FILE_CREATE
//
// Creates a FileMeta object for a specific task in Mythic's database and writes contents to disk with a random UUID filename.
func MythicRPCFileCreate(input MythicRPCFileCreateMessage) MythicRPCFileCreateMessageResponse {
func MythicRPCFileCreate(input MythicRPCFileCreateMessage, authContext RabbitMQAuthContext) MythicRPCFileCreateMessageResponse {
response := MythicRPCFileCreateMessageResponse{
Success: false,
}
@@ -93,18 +93,18 @@ func MythicRPCFileCreate(input MythicRPCFileCreateMessage) MythicRPCFileCreateMe
if len(input.Filename) != 0 {
fileData.Filename = []byte(input.Filename)
}
fileData.OperationID = authContext.OperationID
fileData.OperatorID = authContext.OperatorID
if input.TaskID > 0 {
task := databaseStructs.Task{}
err = database.DB.Get(&task, `SELECT
task.operator_id, task.display_id,
callback.operation_id "callback.operation_id",
task.display_id,
callback.host "callback.host"
FROM
task
JOIN callback ON task.callback_id = callback.id
WHERE
task.id=$1`, input.TaskID)
task.id=$1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "failed to fetch task")
response.Error = "Must supply a valid task ID"
@@ -117,20 +117,17 @@ func MythicRPCFileCreate(input MythicRPCFileCreateMessage) MythicRPCFileCreateMe
}
fileData.TaskID.Int64 = int64(input.TaskID)
fileData.TaskID.Valid = true
fileData.OperationID = task.Callback.OperationID
fileData.OperatorID = task.OperatorID
if input.Comment == "" {
fileData.Comment = fmt.Sprintf("Created from task %d", task.DisplayID)
}
} else if input.PayloadUUID != "" {
payload := databaseStructs.Payload{}
err = database.DB.Get(&payload, `SELECT
payload.operator_id,
payload.operation_id
FROM
payload
WHERE
payload.uuid=$1`, input.PayloadUUID)
payload.uuid=$1 AND payload.operation_id=$2`, input.PayloadUUID, authContext.OperationID)
if err != nil {
logging.LogError(err, "failed to fetch payload uuid")
response.Error = "Must supply a valid payload UUID"
@@ -141,22 +138,18 @@ func MythicRPCFileCreate(input MythicRPCFileCreateMessage) MythicRPCFileCreateMe
} else {
fileData.Host = strings.ToUpper(input.TargetHostName)
}
fileData.OperationID = payload.OperationID
fileData.OperatorID = payload.OperatorID
if input.Comment == "" {
fileData.Comment = fmt.Sprintf("Created from payload %s", input.PayloadUUID)
}
} else if input.AgentCallbackID != "" {
callback := databaseStructs.Callback{}
err = database.DB.Get(&callback, `SELECT
callback.operator_id,
callback.operation_id,
callback.display_id,
callback.host
FROM
callback
WHERE
callback.agent_callback_id=$1`, input.AgentCallbackID)
callback.agent_callback_id=$1 AND callback.operation_id=$2`, input.AgentCallbackID, authContext.OperationID)
if err != nil {
logging.LogError(err, "failed to find agent callback id")
response.Error = "Must supply a valid agent callback id"
@@ -167,52 +160,54 @@ func MythicRPCFileCreate(input MythicRPCFileCreateMessage) MythicRPCFileCreateMe
} else {
fileData.Host = strings.ToUpper(input.TargetHostName)
}
fileData.OperationID = callback.OperationID
fileData.OperatorID = callback.OperatorID
if input.Comment == "" {
fileData.Comment = fmt.Sprintf("Created from callback %d", callback.DisplayID)
}
} else if input.OperationID > 0 {
fileData.OperationID = input.OperationID
if input.OperatorID > 0 {
fileData.OperatorID = input.OperatorID
} else {
response.Error = "Must supply a valid OperatorID if you're also supplying an OperationID"
return response
}
} else {
response.Error = "Must supply a task ID, payload UUID, or agent callback id. Alternatively, you can supply an OperationID and OperatorID"
return response
}
if statement, err := database.DB.PrepareNamed(`INSERT INTO filemeta
(filename,total_chunks,chunks_received,chunk_size,path,operation_id,complete,comment,operator_id,delete_after_fetch,md5,sha1,agent_file_id,full_remote_path,task_id,is_screenshot,is_download_from_agent,host,size)
VALUES (:filename, :total_chunks, :chunks_received, :chunk_size, :path, :operation_id, :complete, :comment, :operator_id, :delete_after_fetch, :md5, :sha1, :agent_file_id, :full_remote_path, :task_id, :is_screenshot, :is_download_from_agent, :host, :size)
RETURNING id`); err != nil {
if authContext.APITokensID > 0 {
fileData.APITokensID.Valid = true
fileData.APITokensID.Int64 = int64(authContext.APITokensID)
}
if authContext.EventStepInstanceID > 0 {
fileData.EventStepInstanceID.Valid = true
fileData.EventStepInstanceID.Int64 = int64(authContext.EventStepInstanceID)
}
statement, err := database.DB.PrepareNamed(`INSERT INTO filemeta
(filename,total_chunks,chunks_received,chunk_size,path,operation_id,complete,comment,operator_id,delete_after_fetch,md5,sha1,agent_file_id,full_remote_path,task_id,is_screenshot,is_download_from_agent,host,size,apitokens_id,eventstepinstance_id)
VALUES (:filename, :total_chunks, :chunks_received, :chunk_size, :path, :operation_id, :complete, :comment, :operator_id, :delete_after_fetch, :md5, :sha1, :agent_file_id, :full_remote_path, :task_id, :is_screenshot, :is_download_from_agent, :host, :size, :apitokens_id, :eventstepinstance_id)
RETURNING id`)
if err != nil {
logging.LogError(err, "Failed to save file metadata to database")
response.Error = err.Error()
return response
} else if err = statement.Get(&fileData.ID, fileData); err != nil {
}
err = statement.Get(&fileData.ID, fileData)
if err != nil {
logging.LogError(err, "Failed to save file to database")
response.Error = err.Error()
return response
} else {
logging.LogDebug("creating new file", "filedata", fileData)
response.Success = true
response.AgentFileId = fileData.AgentFileID
go EmitFileLog(fileData.ID)
return response
}
logging.LogDebug("creating new file", "filedata", fileData)
response.Success = true
response.AgentFileId = fileData.AgentFileID
go EmitFileLog(fileData.ID)
return response
}
func processMythicRPCFileCreate(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCFileCreateMessage{}
responseMsg := MythicRPCFileCreateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCFileCreate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCFileCreate(incomingMessage, authContext)
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"os"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -27,50 +28,62 @@ func init() {
Queue: MYTHIC_RPC_FILE_GET_CONTENT, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_FILE_GET_CONTENT, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCFileGetContent, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_FILE_READ},
})
}
//MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCFileGetContent(input MythicRPCFileGetContentMessage) MythicRPCFileGetContentMessageResponse {
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCFileGetContent(input MythicRPCFileGetContentMessage, authContext RabbitMQAuthContext) MythicRPCFileGetContentMessageResponse {
response := MythicRPCFileGetContentMessageResponse{
Success: false,
}
file := databaseStructs.Filemeta{
AgentFileID: input.AgentFileID,
}
if err := database.DB.Get(&file, `SELECT id, path FROM filemeta WHERE agent_file_id=$1`, input.AgentFileID); err != nil {
err := database.DB.Get(&file, `SELECT
id, path
FROM filemeta
WHERE agent_file_id=$1 AND operation_id=$2`, input.AgentFileID, authContext.OperationID)
if err != nil {
response.Error = err.Error()
return response
}
if diskFile, err := os.OpenFile(file.Path, os.O_RDONLY, 0644); err != nil {
diskFile, err := os.OpenFile(file.Path, os.O_RDONLY, 0644)
if err != nil {
response.Error = err.Error()
return response
} else if fileInfo, err := diskFile.Stat(); err != nil {
response.Error = err.Error()
return response
} else {
fileContents := make([]byte, fileInfo.Size())
if _, err := diskFile.Read(fileContents); err != nil {
response.Error = err.Error()
return response
} else {
response.Content = fileContents
response.Success = true
return response
}
}
fileInfo, err := diskFile.Stat()
if err != nil {
response.Error = err.Error()
return response
}
fileContents := make([]byte, fileInfo.Size())
_, err = diskFile.Read(fileContents)
if err != nil {
response.Error = err.Error()
return response
}
response.Content = fileContents
response.Success = true
return response
}
func processMythicRPCFileGetContent(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCFileGetContentMessage{}
responseMsg := MythicRPCFileGetContentMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCFileGetContent(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCFileGetContent(incomingMessage, authContext)
}
@@ -6,6 +6,7 @@ import (
"errors"
"time"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -16,7 +17,6 @@ type MythicRPCFileSearchMessage struct {
TaskID int `json:"task_id"`
CallbackID int `json:"callback_id"`
Filename string `json:"filename"`
OperationId int `json:"operation_id"`
LimitByCallback bool `json:"limit_by_callback"`
MaxResults int `json:"max_results"`
Comment string `json:"comment"`
@@ -56,22 +56,22 @@ func init() {
Queue: MYTHIC_RPC_FILE_SEARCH,
RoutingKey: MYTHIC_RPC_FILE_SEARCH,
Handler: processMythicRPCFileSearch,
Scopes: []string{mythicjwt.SCOPE_FILE_READ},
})
}
func MythicRPCFileSearch(input MythicRPCFileSearchMessage) MythicRPCFileSearchMessageResponse {
func MythicRPCFileSearch(input MythicRPCFileSearchMessage, authContext RabbitMQAuthContext) MythicRPCFileSearchMessageResponse {
response := MythicRPCFileSearchMessageResponse{
Success: false,
}
var callbackId int
var operationId int
callbackId := 0
if input.AgentFileID != "" {
// the search is for a specific fileID, so just fetch it and return
fileMeta := databaseStructs.Filemeta{}
err := database.DB.Get(&fileMeta, `SELECT
filemeta.*
FROM filemeta
WHERE filemeta.agent_file_id=$1`, input.AgentFileID)
WHERE filemeta.agent_file_id=$1 AND filemeta.operation_id=$2`, input.AgentFileID, authContext.OperationID)
if errors.Is(err, sql.ErrNoRows) {
payload := databaseStructs.Payload{}
err = database.DB.Get(&payload, `SELECT
@@ -81,12 +81,13 @@ func MythicRPCFileSearch(input MythicRPCFileSearchMessage) MythicRPCFileSearchMe
filemeta.operation_id "filemeta.operation_id"
FROM payload
JOIN filemeta ON payload.file_id = filemeta.id
WHERE payload.uuid=$1`, input.AgentFileID)
WHERE payload.uuid=$1 AND payload.operation_id=$2`, input.AgentFileID, authContext.OperationID)
if errors.Is(err, sql.ErrNoRows) {
logging.LogError(err, "Failed to get file data from the database, it's not a payload uuid or file uuid")
response.Error = err.Error()
return response
} else if err != nil {
}
if err != nil {
logging.LogError(err, "Failed to get file data from database")
response.Error = err.Error()
return response
@@ -101,33 +102,31 @@ func MythicRPCFileSearch(input MythicRPCFileSearchMessage) MythicRPCFileSearchMe
response.Files = append(response.Files, convertFileMetaToFileData(fileMeta))
return response
} else if input.CallbackID > 0 {
}
if input.CallbackID > 0 {
callback := databaseStructs.Callback{ID: input.CallbackID}
if err := database.DB.Get(&callback, `SELECT operation_id, id FROM callback WHERE id=$1`, callback.ID); err != nil {
err := database.DB.Get(&callback, `
SELECT operation_id, id
FROM callback
WHERE id=$1 AND operation_id=$2`, callback.ID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to get operation in MythicRPCFileSearch")
response.Error = err.Error()
return response
} else {
operationId = callback.OperationID
callbackId = input.CallbackID
}
callbackId = input.CallbackID
} else if input.TaskID > 0 {
task := databaseStructs.Task{ID: input.TaskID}
if err := database.DB.Get(&task, `SELECT operation_id, callback_id FROM task WHERE id=$1`, task.ID); err != nil {
err := database.DB.Get(&task, `SELECT
operation_id, callback_id
FROM task
WHERE id=$1 AND operation_id=$2`, task.ID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to get task from Mythic")
response.Error = err.Error()
return response
} else {
operationId = task.OperationID
callbackId = task.CallbackID
}
} else if input.OperationId == 0 {
response.Success = false
response.Error = "Must supply TaskID, CallbackID or OperationID if not searching for a specific file by AgentFileID"
return response
} else {
operationId = input.OperationId
callbackId = 0
callbackId = task.CallbackID
}
files := []databaseStructs.Filemeta{}
comment := "%%"
@@ -145,7 +144,7 @@ func MythicRPCFileSearch(input MythicRPCFileSearchMessage) MythicRPCFileSearchMe
is_screenshot=$5 AND deleted=false and filemeta.operation_id=$6 and filemeta.copy_of_file_id is NULL
ORDER BY id DESC`
searchParameters := []interface{}{
comment, filename, input.IsPayload, input.IsDownloadFromAgent, input.IsScreenshot, operationId,
comment, filename, input.IsPayload, input.IsDownloadFromAgent, input.IsScreenshot, authContext.OperationID,
}
if input.MaxResults > 0 && !input.LimitByCallback {
searchString += ` LIMIT $7`
@@ -164,7 +163,10 @@ func MythicRPCFileSearch(input MythicRPCFileSearchMessage) MythicRPCFileSearchMe
if input.LimitByCallback {
if file.TaskID.Valid {
var fileCallbackID int
if err := database.DB.Get(&fileCallbackID, `SELECT callback_id FROM task WHERE id=$1`, file.TaskID.Int64); err != nil {
if err := database.DB.Get(&fileCallbackID, `
SELECT callback_id
FROM task
WHERE id=$1 AND operation_id=$2`, file.TaskID.Int64, authContext.OperationID); err != nil {
logging.LogError(err, "Failed to get the task information for callback")
} else if fileCallbackID == callbackId {
//logging.LogInfo("found matching callback_id", "filename", string(file.Filename), "maxResults", input.MaxResults)
@@ -233,11 +235,16 @@ func processMythicRPCFileSearch(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCFileSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCFileSearch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCFileSearch(incomingMessage, authContext)
}
@@ -7,6 +7,7 @@ import (
"fmt"
"os"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -35,19 +36,23 @@ func init() {
Queue: MYTHIC_RPC_FILE_UPDATE, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_FILE_UPDATE, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCFileUpdate, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_FILE_WRITE},
})
}
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCFileUpdate(input MythicRPCFileUpdateMessage) MythicRPCFileUpdateMessageResponse {
func MythicRPCFileUpdate(input MythicRPCFileUpdateMessage, authContext RabbitMQAuthContext) MythicRPCFileUpdateMessageResponse {
response := MythicRPCFileUpdateMessageResponse{
Success: false,
}
file := databaseStructs.Filemeta{
AgentFileID: input.AgentFileID,
}
if err := database.DB.Get(&file, `SELECT id, "comment", filename, "path", delete_after_fetch
FROM filemeta WHERE agent_file_id=$1`, input.AgentFileID); err != nil {
err := database.DB.Get(&file, `
SELECT id, "comment", filename, "path", delete_after_fetch
FROM filemeta
WHERE agent_file_id=$1 AND operation_id=$2`, input.AgentFileID, authContext.OperationID)
if err != nil {
response.Error = err.Error()
return response
}
@@ -61,95 +66,107 @@ func MythicRPCFileUpdate(input MythicRPCFileUpdateMessage) MythicRPCFileUpdateMe
file.DeleteAfterFetch = *input.DeleteAfterFetch
}
if input.AppendContents != nil {
if diskFile, err := os.OpenFile(file.Path, os.O_APPEND|os.O_RDWR, 0644); err != nil {
diskFile, err := os.OpenFile(file.Path, os.O_APPEND|os.O_RDWR, 0644)
if err != nil {
response.Error = err.Error()
return response
} else if _, err = diskFile.Write(*input.AppendContents); err != nil {
}
_, err = diskFile.Write(*input.AppendContents)
if err != nil {
response.Error = err.Error()
return response
} else if err = diskFile.Close(); err != nil {
}
err = diskFile.Close()
if err != nil {
response.Error = err.Error()
return response
} else if diskFile, err = os.OpenFile(file.Path, os.O_RDONLY, 0644); err != nil {
}
diskFile, err = os.OpenFile(file.Path, os.O_RDONLY, 0644)
if err != nil {
response.Error = err.Error()
return response
} else if fileInfo, err := diskFile.Stat(); err != nil {
}
fileInfo, err := diskFile.Stat()
if err != nil {
response.Error = err.Error()
return response
} else {
fileContents := make([]byte, fileInfo.Size())
if _, err := diskFile.Read(fileContents); err != nil {
response.Error = err.Error()
return response
} else {
// calculate new MD5/SHA1 hashes
sha1Sum := sha1.Sum(fileContents)
file.Sha1 = fmt.Sprintf("%x", sha1Sum)
md5Sum := md5.Sum(fileContents)
file.Md5 = fmt.Sprintf("%x", md5Sum)
if _, err := database.DB.NamedExec(`UPDATE filemeta SET
}
fileContents := make([]byte, fileInfo.Size())
_, err = diskFile.Read(fileContents)
if err != nil {
response.Error = err.Error()
return response
}
// calculate new MD5/SHA1 hashes
sha1Sum := sha1.Sum(fileContents)
file.Sha1 = fmt.Sprintf("%x", sha1Sum)
md5Sum := md5.Sum(fileContents)
file.Md5 = fmt.Sprintf("%x", md5Sum)
_, err = database.DB.NamedExec(`UPDATE filemeta SET
"comment"=:comment, filename=:filename, sha1=:sha1, md5=:md5, delete_after_fetch=:delete_after_fetch
WHERE id=:id`, file); err != nil {
response.Error = err.Error()
return response
} else {
response.Success = true
go EmitFileLog(file.ID)
return response
}
}
WHERE id=:id`, file)
if err != nil {
response.Error = err.Error()
return response
}
} else if input.ReplaceContents != nil {
if diskFile, err := os.OpenFile(file.Path, os.O_TRUNC|os.O_RDWR, 0644); err != nil {
response.Success = true
go EmitFileLog(file.ID)
return response
}
if input.ReplaceContents != nil {
diskFile, err := os.OpenFile(file.Path, os.O_TRUNC|os.O_RDWR, 0644)
if err != nil {
response.Error = err.Error()
return response
} else if _, err = diskFile.Write(*input.ReplaceContents); err != nil {
}
_, err = diskFile.Write(*input.ReplaceContents)
if err != nil {
response.Error = err.Error()
return response
} else if err = diskFile.Close(); err != nil {
}
err = diskFile.Close()
if err != nil {
response.Error = err.Error()
return response
} else {
// calculate new MD5/SHA1 hashes
sha1Sum := sha1.Sum(*input.ReplaceContents)
file.Sha1 = fmt.Sprintf("%x", sha1Sum)
md5Sum := md5.Sum(*input.ReplaceContents)
file.Md5 = fmt.Sprintf("%x", md5Sum)
if _, err := database.DB.NamedExec(`UPDATE filemeta SET
}
// calculate new MD5/SHA1 hashes
sha1Sum := sha1.Sum(*input.ReplaceContents)
file.Sha1 = fmt.Sprintf("%x", sha1Sum)
md5Sum := md5.Sum(*input.ReplaceContents)
file.Md5 = fmt.Sprintf("%x", md5Sum)
_, err = database.DB.NamedExec(`UPDATE filemeta SET
comment=:comment, filename=:filename, sha1=:sha1, md5=:md5, delete_after_fetch=:delete_after_fetch
WHERE id=:id`, file); err != nil {
response.Error = err.Error()
return response
} else {
response.Success = true
return response
}
}
} else if input.Delete {
if err := os.Remove(file.Path); err != nil {
WHERE id=:id`, file)
if err != nil {
response.Error = err.Error()
return response
} else {
if _, err := database.DB.NamedExec(`UPDATE filemeta SET
"comment"=:comment, filename=:filename, deleted=true, delete_after_fetch=:delete_after_fetch
WHERE id=:id`, file); err != nil {
response.Error = err.Error()
return response
} else {
response.Success = true
return response
}
}
} else {
if _, err := database.DB.NamedExec(`UPDATE filemeta SET
response.Success = true
return response
}
if input.Delete {
err = os.Remove(file.Path)
if err != nil {
response.Error = err.Error()
return response
}
_, err = database.DB.NamedExec(`UPDATE filemeta SET
"comment"=:comment, filename=:filename, deleted=true, delete_after_fetch=:delete_after_fetch
WHERE id=:id`, file)
if err != nil {
response.Error = err.Error()
return response
}
response.Success = true
return response
}
_, err = database.DB.NamedExec(`UPDATE filemeta SET
"comment"=:comment, filename=:filename, delete_after_fetch=:delete_after_fetch
WHERE id=:id
`, file); err != nil {
response.Error = err.Error()
return response
}
`, file)
if err != nil {
response.Error = err.Error()
return response
}
response.Success = true
return response
@@ -159,11 +176,16 @@ func processMythicRPCFileUpdate(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCFileUpdateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct", "body", msg.Body)
responseMsg.Error = err.Error()
} else {
return MythicRPCFileUpdate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCFileUpdate(incomingMessage, authContext)
}
@@ -3,6 +3,7 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -25,11 +26,12 @@ func init() {
Queue: MYTHIC_RPC_FILEBROWSER_CREATE,
RoutingKey: MYTHIC_RPC_FILEBROWSER_CREATE,
Handler: processMythicRPCFileBrowserCreate,
Scopes: []string{mythicjwt.SCOPE_BROWSER_WRITE},
})
}
// Endpoint: MYTHIC_RPC_FILEBROWSER_CREATE
func MythicRPCFileBrowserCreate(input MythicRPCFileBrowserCreateMessage) MythicRPCFileBrowserCreateMessageResponse {
func MythicRPCFileBrowserCreate(input MythicRPCFileBrowserCreateMessage, authContext RabbitMQAuthContext) MythicRPCFileBrowserCreateMessageResponse {
response := MythicRPCFileBrowserCreateMessageResponse{
Success: false,
}
@@ -46,7 +48,7 @@ func MythicRPCFileBrowserCreate(input MythicRPCFileBrowserCreateMessage) MythicR
FROM task
JOIN callback ON task.callback_id = callback.id
JOIN payload ON callback.registered_payload_id = payload.id
WHERE task.id = $1`, input.TaskID)
WHERE task.id = $1 AND task.operation_id=$2`, input.TaskID, authContext)
if err != nil {
logging.LogError(err, "Failed to fetch task")
response.Error = err.Error()
@@ -78,5 +80,10 @@ func processMythicRPCFileBrowserCreate(msg amqp.Delivery) interface{} {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCFileBrowserCreate(incomingMessage)
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCFileBrowserCreate(incomingMessage, authContext)
}
@@ -2,6 +2,7 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/logging"
"github.com/its-a-feature/Mythic/utils"
amqp "github.com/rabbitmq/amqp091-go"
@@ -22,6 +23,7 @@ func init() {
Queue: MYTHIC_RPC_FILEBROWSER_PARSE_PATH,
RoutingKey: MYTHIC_RPC_FILEBROWSER_PARSE_PATH,
Handler: processMythicRPCFileBrowserParsePath,
Scopes: []string{},
})
}
@@ -44,11 +46,11 @@ func processMythicRPCFileBrowserParsePath(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCFileBrowserParsePathMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCFileBrowserParsePath(incomingMessage)
return responseMsg
}
return responseMsg
return MythicRPCFileBrowserParsePath(incomingMessage)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -24,16 +26,17 @@ func init() {
Queue: MYTHIC_RPC_FILEBROWSER_REMOVE,
RoutingKey: MYTHIC_RPC_FILEBROWSER_REMOVE,
Handler: processMythicRPCFileBrowserRemove,
Scopes: []string{mythicjwt.SCOPE_BROWSER_WRITE},
})
}
// Endpoint: MYTHIC_RPC_FILEBROWSER_REMOVE
func MythicRPCFileBrowserRemove(input MythicRPCFileBrowserRemoveMessage) MythicRPCFileBrowserRemoveMessageResponse {
func MythicRPCFileBrowserRemove(input MythicRPCFileBrowserRemoveMessage, authContext RabbitMQAuthContext) MythicRPCFileBrowserRemoveMessageResponse {
response := MythicRPCFileBrowserRemoveMessageResponse{
Success: false,
}
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
err := database.DB.Get(&task, `SELECT
task.id, task.status, task.completed, task.status_timestamp_processed, task.operator_id, task.operation_id,
callback.host "callback.host",
callback.user "callback.user",
@@ -44,29 +47,36 @@ func MythicRPCFileBrowserRemove(input MythicRPCFileBrowserRemoveMessage) MythicR
FROM task
JOIN callback ON task.callback_id = callback.id
JOIN payload ON callback.registered_payload_id = payload.id
WHERE task.id = $1`, input.TaskID); err != nil {
WHERE task.id = $1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch task")
response.Error = err.Error()
return response
} else if err := handleAgentMessagePostResponseRemovedFiles(task, &input.RemovedFiles); err != nil {
}
err = handleAgentMessagePostResponseRemovedFiles(task, &input.RemovedFiles)
if err != nil {
logging.LogError(err, "Failed to create processes in MythicRPCProcessCreate")
response.Error = err.Error()
return response
} else {
response.Success = true
return response
}
response.Success = true
return response
}
func processMythicRPCFileBrowserRemove(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCFileBrowserRemoveMessage{}
responseMsg := MythicRPCFileBrowserRemoveMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCFileBrowserRemove(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCFileBrowserRemove(incomingMessage, authContext)
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
@@ -29,11 +30,17 @@ func init() {
Queue: MYTHIC_RPC_HANDLE_AGENT_JSON,
RoutingKey: MYTHIC_RPC_HANDLE_AGENT_JSON,
Handler: processMythicRPCHandleAgentJson,
Scopes: []string{
mythicjwt.SCOPE_CALLBACK_WRITE,
mythicjwt.SCOPE_RESPONSE_WRITE,
mythicjwt.SCOPE_FILE_WRITE,
mythicjwt.SCOPE_CREDENTIAL_WRITE,
},
})
}
// Endpoint: MYTHIC_RPC_HANDLE_AGENT_JSON
func MythicRPCHandleAgentJson(input MythicRPCHandleAgentJsonMessage) MythicRPCHandleAgentJsonMessageResponse {
func MythicRPCHandleAgentJson(input MythicRPCHandleAgentJsonMessage, authContext RabbitMQAuthContext) MythicRPCHandleAgentJsonMessageResponse {
response := MythicRPCHandleAgentJsonMessageResponse{
Success: false,
}
@@ -42,7 +49,7 @@ func MythicRPCHandleAgentJson(input MythicRPCHandleAgentJsonMessage) MythicRPCHa
err := database.DB.Get(&callback, `SELECT
operation_id, id, display_id, agent_callback_id
FROM callback
WHERE id=$1`, *input.CallbackID)
WHERE id=$1 AND operation_id=$2`, *input.CallbackID, authContext.OperationID)
if err != nil {
response.Success = false
response.Error = err.Error()
@@ -52,7 +59,7 @@ func MythicRPCHandleAgentJson(input MythicRPCHandleAgentJsonMessage) MythicRPCHa
err := database.DB.Get(&callback, `SELECT
operation_id, id, display_id, agent_callback_id
FROM callback
WHERE agent_callback_id=$1`, *input.AgentCallbackID)
WHERE agent_callback_id=$1 AND operation_id=$2`, *input.AgentCallbackID, authContext.OperationID)
if err != nil {
response.Success = false
response.Error = err.Error()
@@ -96,11 +103,16 @@ func processMythicRPCHandleAgentJson(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCHandleAgentJsonMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCHandleAgentJson(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCHandleAgentJson(incomingMessage, authContext)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -24,11 +26,12 @@ func init() {
Queue: MYTHIC_RPC_KEYLOG_CREATE,
RoutingKey: MYTHIC_RPC_KEYLOG_CREATE,
Handler: processMythicRPCKeylogCreate,
Scopes: []string{mythicjwt.SCOPE_RESPONSE_WRITE},
})
}
// Endpoint: MYTHIC_RPC_PROCESS_CREATE
func MythicRPCKeylogCreate(input MythicRPCKeylogCreateMessage) MythicRPCKeylogCreateMessageResponse {
func MythicRPCKeylogCreate(input MythicRPCKeylogCreateMessage, authContext RabbitMQAuthContext) MythicRPCKeylogCreateMessageResponse {
response := MythicRPCKeylogCreateMessageResponse{
Success: false,
}
@@ -37,8 +40,9 @@ func MythicRPCKeylogCreate(input MythicRPCKeylogCreateMessage) MythicRPCKeylogCr
return response
}
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
err := database.DB.Get(&task, `SELECT
task.id, task.status, task.completed, task.status_timestamp_processed, task.operator_id, task.operation_id,
task.apitokens_id, task.eventstepinstance_id,
callback.host "callback.host",
callback.user "callback.user",
callback.id "callback.id",
@@ -48,29 +52,36 @@ func MythicRPCKeylogCreate(input MythicRPCKeylogCreateMessage) MythicRPCKeylogCr
FROM task
JOIN callback ON task.callback_id = callback.id
JOIN payload ON callback.registered_payload_id = payload.id
WHERE task.id = $1`, input.TaskID); err != nil {
WHERE task.id = $1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch task")
response.Error = err.Error()
return response
} else if err := handleAgentMessagePostResponseKeylogs(task, &input.Keylogs); err != nil {
}
err = handleAgentMessagePostResponseKeylogs(task, &input.Keylogs)
if err != nil {
logging.LogError(err, "Failed to create processes in MythicRPCProcessCreate")
response.Error = err.Error()
return response
} else {
response.Success = true
return response
}
response.Success = true
return response
}
func processMythicRPCKeylogCreate(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCKeylogCreateMessage{}
responseMsg := MythicRPCKeylogCreateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCKeylogCreate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCKeylogCreate(incomingMessage, authContext)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/mitchellh/mapstructure"
"github.com/its-a-feature/Mythic/database"
@@ -11,7 +13,6 @@ import (
)
type MythicRPCKeylogSearchMessage struct {
TaskID int `json:"task_id"` //required
SearchKeylogs MythicRPCKeylogSearchKeylogData `json:"keylogs"`
}
type MythicRPCKeylogSearchMessageResponse struct {
@@ -31,69 +32,62 @@ func init() {
Queue: MYTHIC_RPC_KEYLOG_SEARCH,
RoutingKey: MYTHIC_RPC_KEYLOG_SEARCH,
Handler: processMythicRPCKeylogSearch,
Scopes: []string{mythicjwt.SCOPE_RESPONSE_READ},
})
}
// Endpoint: MYTHIC_RPC_KEYLOG_SEARCH
func MythicRPCKeylogSearch(input MythicRPCKeylogSearchMessage) MythicRPCKeylogSearchMessageResponse {
func MythicRPCKeylogSearch(input MythicRPCKeylogSearchMessage, authContext RabbitMQAuthContext) MythicRPCKeylogSearchMessageResponse {
response := MythicRPCKeylogSearchMessageResponse{
Success: false,
}
paramDict := make(map[string]interface{})
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
task.id,
callback.operation_id "callback.operation_id",
callback.host "callback.host"
FROM task
JOIN callback ON task.callback_id = callback.id
WHERE task.id=$1`, input.TaskID); err != nil {
keylogs := []databaseStructs.Keylog{}
paramDict["operation_id"] = authContext.OperationID
searchString := `SELECT * FROM keylog WHERE operation_id=:operation_id `
if input.SearchKeylogs.User != nil {
paramDict["user"] = "%" + *input.SearchKeylogs.User + "%"
searchString += "AND user ILIKE :user "
}
if input.SearchKeylogs.WindowTitle != nil {
paramDict["window_title"] = "%" + *input.SearchKeylogs.WindowTitle + "%"
searchString += "AND window ILIKE :window_title "
}
if input.SearchKeylogs.Keystrokes != nil {
paramDict["keystrokes"] = "%" + string(*input.SearchKeylogs.Keystrokes) + "%"
searchString += "AND keystrokes LIKE :keystrokes "
}
searchString += " ORDER BY id DESC"
err := database.DB.Select(&keylogs, searchString, paramDict)
if err != nil {
response.Error = err.Error()
return response
} else {
keylogs := []databaseStructs.Keylog{}
paramDict["operation_id"] = task.Callback.OperationID
searchString := `SELECT * FROM keylog WHERE operation_id=:operation_id `
if input.SearchKeylogs.User != nil {
paramDict["user"] = "%" + *input.SearchKeylogs.User + "%"
searchString += "AND user ILIKE :user "
}
if input.SearchKeylogs.WindowTitle != nil {
paramDict["window_title"] = "%" + *input.SearchKeylogs.WindowTitle + "%"
searchString += "AND window ILIKE :window_title "
}
if input.SearchKeylogs.Keystrokes != nil {
paramDict["keystrokes"] = "%" + string(*input.SearchKeylogs.Keystrokes) + "%"
searchString += "AND keystrokes LIKE :keystrokes "
}
searchString += " ORDER BY id DESC"
if err := database.DB.Select(&keylogs, searchString, paramDict); err != nil {
response.Error = err.Error()
return response
} else {
returnedProcesses := []MythicRPCKeylogSearchKeylogData{}
if err := mapstructure.Decode(keylogs, &returnedProcesses); err != nil {
response.Error = err.Error()
return response
} else {
response.Success = true
response.Keylogs = returnedProcesses
return response
}
}
}
returnedProcesses := []MythicRPCKeylogSearchKeylogData{}
err = mapstructure.Decode(keylogs, &returnedProcesses)
if err != nil {
response.Error = err.Error()
return response
}
response.Success = true
response.Keylogs = returnedProcesses
return response
}
func processMythicRPCKeylogSearch(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCKeylogSearchMessage{}
responseMsg := MythicRPCKeylogSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCKeylogSearch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCKeylogSearch(incomingMessage, authContext)
}
@@ -3,19 +3,13 @@ package rabbitmq
import (
"encoding/json"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
"github.com/its-a-feature/Mythic/logging"
amqp "github.com/rabbitmq/amqp091-go"
)
type MythicRPCOperationEventLogCreateMessage struct {
// three optional ways to specify the operation
TaskID *int `json:"task_id"`
CallbackID *int `json:"callback_id"`
AgentCallbackID *string `json:"callback_agent_id"`
OperationID *int `json:"operation_id"`
// the data to store
Message string `json:"message"`
MessageLevel database.MESSAGE_TYPE `json:"level"` //info or warning
@@ -32,50 +26,20 @@ func init() {
Queue: MYTHIC_RPC_EVENTLOG_CREATE,
RoutingKey: MYTHIC_RPC_EVENTLOG_CREATE,
Handler: processMythicRPCOperationEventLogCreate,
Scopes: []string{mythicjwt.SCOPE_EVENTLOG_WRITE},
})
}
// Endpoint: MYTHIC_RPC_OPERATIONEVENTLOG_CREATE
func MythicRPCOperationEventLogCreate(input MythicRPCOperationEventLogCreateMessage) MythicRPCOperationEventLogCreateMessageResponse {
func MythicRPCOperationEventLogCreate(input MythicRPCOperationEventLogCreateMessage, authContext RabbitMQAuthContext) MythicRPCOperationEventLogCreateMessageResponse {
response := MythicRPCOperationEventLogCreateMessageResponse{
Success: false,
}
operationId := 0
if input.TaskID != nil {
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT operation_id FROM task WHERE id=$1`, input.TaskID); err != nil {
logging.LogError(err, "Failed to find task for creating operation event log")
response.Error = err.Error()
return response
} else {
operationId = task.OperationID
}
} else if input.CallbackID != nil {
callback := databaseStructs.Callback{}
if err := database.DB.Get(&callback, `SELECT operation_id FROM callback WHERE id=$1`, input.CallbackID); err != nil {
logging.LogError(err, "Failed to find callback for creating operation event log")
response.Error = err.Error()
return response
} else {
operationId = callback.OperationID
}
} else if input.AgentCallbackID != nil {
callback := databaseStructs.Callback{}
if err := database.DB.Get(&callback, `SELECT operation_id FROM callback WHERE agent_callback_id=$1`, input.AgentCallbackID); err != nil {
logging.LogError(err, "Failed to find callback for creating operation event log")
response.Error = err.Error()
return response
} else {
operationId = callback.OperationID
}
} else if input.OperationID != nil {
operationId = *input.OperationID
}
if input.MessageLevel == "warning" {
input.MessageLevel = database.MESSAGE_LEVEL_INFO
input.Warning = true
}
SendAllOperationsMessage(input.Message, operationId, "", input.MessageLevel, input.Warning)
SendAllOperationsMessageWithAuth(input.Message, authContext.OperationID, "", input.MessageLevel, input.Warning, authContext)
response.Success = true
return response
}
@@ -84,11 +48,16 @@ func processMythicRPCOperationEventLogCreate(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCOperationEventLogCreateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCOperationEventLogCreate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCOperationEventLogCreate(incomingMessage, authContext)
}
@@ -4,6 +4,8 @@ import (
"database/sql"
"encoding/json"
"errors"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -25,32 +27,33 @@ func init() {
Queue: MYTHIC_RPC_PAYLOAD_ADD_COMMAND,
RoutingKey: MYTHIC_RPC_PAYLOAD_ADD_COMMAND,
Handler: processMythicRPCPayloadAddCommand,
Scopes: []string{mythicjwt.SCOPE_PAYLOAD_WRITE},
})
}
// Endpoint: MYTHIC_RPC_PAYLOAD_ADD_COMMAND
func MythicRPCPayloadAddCommand(input MythicRPCPayloadAddCommandMessage) MythicRPCPayloadAddCommandMessageResponse {
func MythicRPCPayloadAddCommand(input MythicRPCPayloadAddCommandMessage, authContext RabbitMQAuthContext) MythicRPCPayloadAddCommandMessageResponse {
response := MythicRPCPayloadAddCommandMessageResponse{
Success: false,
}
payload := databaseStructs.Payload{}
if err := database.DB.Get(&payload, `SELECT payload.id, payload_type_id
err := database.DB.Get(&payload, `SELECT
payload.id, payload_type_id
FROM payload
WHERE uuid=$1`, input.PayloadUUID); err != nil {
WHERE uuid=$1 AND operation_id=$2`, input.PayloadUUID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch payload in MythicRPCPayloadAddCommand")
response.Error = err.Error()
return response
} else {
if err := PayloadAddCommand(payload.ID, payload.PayloadTypeID, input.Commands); err != nil {
logging.LogError(err, "Failed to add commands to callback")
response.Error = err.Error()
return response
} else {
response.Success = true
return response
}
}
err = PayloadAddCommand(payload.ID, payload.PayloadTypeID, input.Commands)
if err != nil {
logging.LogError(err, "Failed to add commands to callback")
response.Error = err.Error()
return response
}
response.Success = true
return response
}
func PayloadAddCommand(PayloadID int, payloadtypeID int, commands []string) error {
for _, command := range commands {
@@ -97,11 +100,16 @@ func processMythicRPCPayloadAddCommand(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCPayloadAddCommandMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCPayloadAddCommand(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCPayloadAddCommand(incomingMessage, authContext)
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"strings"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -29,27 +30,31 @@ func init() {
Queue: MYTHIC_RPC_PAYLOAD_CREATE_FROM_SCRATCH, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_PAYLOAD_CREATE_FROM_SCRATCH, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCPayloadCreateFromScratch, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_PAYLOAD_WRITE},
})
}
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCPayloadCreateFromScratch(input MythicRPCPayloadCreateFromScratchMessage) MythicRPCPayloadCreateFromScratchMessageResponse {
func MythicRPCPayloadCreateFromScratch(input MythicRPCPayloadCreateFromScratchMessage, authContext RabbitMQAuthContext) MythicRPCPayloadCreateFromScratchMessageResponse {
response := MythicRPCPayloadCreateFromScratchMessageResponse{
Success: false,
}
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
operator_id, operation_id
err := database.DB.Get(&task, `SELECT
operator_id, operation_id, apitokens_id, eventstepinstance_id
FROM task
WHERE id=$1`, input.TaskID); err != nil {
WHERE id=$1 AND operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to get operator_id from task when generating payload")
response.Error = err.Error()
return response
}
input.PayloadConfiguration.APITokensID = authContext.APITokensID
input.PayloadConfiguration.EventStepInstance = authContext.EventStepInstanceID
newUUID, newID, err := RegisterNewPayload(input.PayloadConfiguration, &databaseStructs.Operatoroperation{
CurrentOperation: databaseStructs.Operation{ID: task.OperationID},
CurrentOperator: databaseStructs.Operator{ID: task.OperatorID},
})
}, authContext)
if err != nil {
response.Error = err.Error()
return response
@@ -78,11 +83,16 @@ func processMythicRPCPayloadCreateFromScratch(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCPayloadCreateFromScratchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCPayloadCreateFromScratch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCPayloadCreateFromScratch(incomingMessage, authContext)
}
@@ -8,6 +8,7 @@ import (
"slices"
"strings"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
mythicCrypto "github.com/its-a-feature/Mythic/crypto"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
@@ -38,18 +39,19 @@ func init() {
Queue: MYTHIC_RPC_PAYLOAD_CREATE_FROM_UUID, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_PAYLOAD_CREATE_FROM_UUID, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCPayloadCreateFromUUID, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_PAYLOAD_WRITE},
})
}
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCPayloadCreateFromUUID(input MythicRPCPayloadCreateFromUUIDMessage) MythicRPCPayloadCreateFromUUIDMessageResponse {
func MythicRPCPayloadCreateFromUUID(input MythicRPCPayloadCreateFromUUIDMessage, authContext RabbitMQAuthContext) MythicRPCPayloadCreateFromUUIDMessageResponse {
response := MythicRPCPayloadCreateFromUUIDMessageResponse{
Success: false,
}
// first get all of the payload information
payloadConfiguration := PayloadConfiguration{}
payload := databaseStructs.Payload{}
if err := database.DB.Get(&payload, `SELECT
err := database.DB.Get(&payload, `SELECT
payload.id, payload.description, payload.uuid, payload.os, payload.wrapped_payload_id, payload.operation_id,
payloadtype.name "payloadtype.name",
filemeta.filename "filemeta.filename"
@@ -58,80 +60,96 @@ func MythicRPCPayloadCreateFromUUID(input MythicRPCPayloadCreateFromUUIDMessage)
JOIN payloadtype ON payload.payload_type_id = payloadtype.id
JOIN filemeta ON payload.file_id = filemeta.id
WHERE
payload.uuid=$1`, input.PayloadUUID); err != nil {
payload.uuid=$1 AND payload.operation_id=$2`, input.PayloadUUID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to get payload")
response.Error = err.Error()
return response
}
if input.NewDescription != nil {
payloadConfiguration.Description = *input.NewDescription
} else {
if input.NewDescription != nil {
payloadConfiguration.Description = *input.NewDescription
} else {
payloadConfiguration.Description = payload.Description
}
if input.NewFilename != nil {
payloadConfiguration.Filename = *input.NewFilename
} else {
payloadConfiguration.Filename = string(payload.Filemeta.Filename)
}
payloadConfiguration.SelectedOS = payload.Os
payloadConfiguration.PayloadType = payload.Payloadtype.Name
payloadConfiguration.C2Profiles = GetPayloadC2ProfileInformation(payload)
payloadConfiguration.BuildParameters = GetBuildParameterInformation(payload.ID)
payloadConfiguration.Commands = GetPayloadCommandInformation(payload)
if payload.WrappedPayloadID.Valid {
// get the associated UUID for the wrapped payload
wrappedPayload := databaseStructs.Payload{}
if err := database.DB.Get(&wrappedPayload, `SELECT uuid FROM payload WHERE id=$1`, payload.WrappedPayloadID.Int64); err != nil {
logging.LogError(err, "Failed to fetch wrapped payload information")
response.Error = err.Error()
return response
} else {
payloadConfiguration.WrappedPayloadUUID = wrappedPayload.UuID
}
}
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT operator_id FROM task WHERE id=$1`, input.TaskID); err != nil {
logging.LogError(err, "Failed to get operator_id from task when generating payload")
payloadConfiguration.Description = payload.Description
}
if input.NewFilename != nil {
payloadConfiguration.Filename = *input.NewFilename
} else {
payloadConfiguration.Filename = string(payload.Filemeta.Filename)
}
payloadConfiguration.SelectedOS = payload.Os
payloadConfiguration.PayloadType = payload.Payloadtype.Name
payloadConfiguration.C2Profiles = GetPayloadC2ProfileInformation(payload)
payloadConfiguration.BuildParameters = GetBuildParameterInformation(payload.ID)
payloadConfiguration.Commands = GetPayloadCommandInformation(payload)
if payload.WrappedPayloadID.Valid {
// get the associated UUID for the wrapped payload
wrappedPayload := databaseStructs.Payload{}
err = database.DB.Get(&wrappedPayload, `SELECT
uuid
FROM payload
WHERE id=$1 AND operation_id=$2`, payload.WrappedPayloadID.Int64, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch wrapped payload information")
response.Error = err.Error()
return response
}
if newUUID, newID, err := RegisterNewPayload(payloadConfiguration, &databaseStructs.Operatoroperation{
CurrentOperation: databaseStructs.Operation{ID: payload.OperationID},
CurrentOperator: databaseStructs.Operator{ID: task.OperatorID},
}); err != nil {
response.Error = err.Error()
return response
} else if _, err := database.DB.Exec(`UPDATE payload SET auto_generated=true, task_id=$2 WHERE id=$1`, newID, input.TaskID); err != nil {
logging.LogError(err, "failed to update payload auto_generated status")
response.Error = err.Error()
return response
} else {
if input.RemoteHost != nil {
if _, err := database.DB.Exec(`INSERT INTO payloadonhost
payloadConfiguration.WrappedPayloadUUID = wrappedPayload.UuID
}
task := databaseStructs.Task{}
err = database.DB.Get(&task, `SELECT
operator_id, apitokens_id, eventstepinstance_id
FROM task
WHERE id=$1 AND operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to get operator_id from task when generating payload")
response.Error = err.Error()
return response
}
payloadConfiguration.APITokensID = authContext.APITokensID
payloadConfiguration.EventStepInstance = authContext.EventStepInstanceID
newUUID, newID, err := RegisterNewPayload(payloadConfiguration, &databaseStructs.Operatoroperation{
CurrentOperation: databaseStructs.Operation{ID: payload.OperationID},
CurrentOperator: databaseStructs.Operator{ID: task.OperatorID},
}, authContext)
if err != nil {
response.Error = err.Error()
return response
}
_, err = database.DB.Exec(`UPDATE payload SET auto_generated=true, task_id=$2 WHERE id=$1`, newID, input.TaskID)
if err != nil {
logging.LogError(err, "failed to update payload auto_generated status")
response.Error = err.Error()
return response
}
if input.RemoteHost != nil {
if _, err := database.DB.Exec(`INSERT INTO payloadonhost
(host, payload_id, operation_id, task_id)
VALUES
($1, $2, $3, $4)`, strings.ToUpper(*input.RemoteHost), newID, payload.OperationID, input.TaskID); err != nil {
logging.LogError(err, "Failed to register payload on host")
}
}
response.NewPayloadUUID = newUUID
response.Success = true
return response
logging.LogError(err, "Failed to register payload on host")
}
}
response.NewPayloadUUID = newUUID
response.Success = true
return response
}
func processMythicRPCPayloadCreateFromUUID(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCPayloadCreateFromUUIDMessage{}
responseMsg := MythicRPCPayloadCreateFromUUIDMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCPayloadCreateFromUUID(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCPayloadCreateFromUUID(incomingMessage, authContext)
}
func associateBuildParametersWithPayload(databasePayload databaseStructs.Payload, buildParameters *[]PayloadConfigurationBuildParameter) (map[string]interface{}, error) {
@@ -4,6 +4,7 @@ import (
"encoding/json"
"os"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -27,50 +28,60 @@ func init() {
Queue: MYTHIC_RPC_PAYLOAD_GET_PAYLOAD_CONTENT, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_PAYLOAD_GET_PAYLOAD_CONTENT, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCPayloadGetContent, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_PAYLOAD_READ},
})
}
//MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCPayloadGetContent(input MythicRPCPayloadGetContentMessage) MythicRPCPayloadGetContentMessageResponse {
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCPayloadGetContent(input MythicRPCPayloadGetContentMessage, authContext RabbitMQAuthContext) MythicRPCPayloadGetContentMessageResponse {
response := MythicRPCPayloadGetContentMessageResponse{
Success: false,
}
payload := databaseStructs.Payload{}
if err := database.DB.Get(&payload, `SELECT
err := database.DB.Get(&payload, `SELECT
filemeta.path "filemeta.path"
FROM payload
JOIN filemeta ON payload.file_id = filemeta.id
WHERE payload.uuid = $1`, input.PayloadUUID); err != nil {
WHERE payload.uuid = $1 AND payload.operation_id=$2`, input.PayloadUUID, authContext.OperationID)
if err != nil {
response.Error = err.Error()
return response
} else if diskFile, err := os.OpenFile(payload.Filemeta.Path, os.O_RDONLY, 0644); err != nil {
response.Error = err.Error()
return response
} else if fileInfo, err := diskFile.Stat(); err != nil {
response.Error = err.Error()
return response
} else {
fileContents := make([]byte, fileInfo.Size())
if _, err := diskFile.Read(fileContents); err != nil {
response.Error = err.Error()
return response
} else {
response.Content = fileContents
response.Success = true
return response
}
}
diskFile, err := os.OpenFile(payload.Filemeta.Path, os.O_RDONLY, 0644)
if err != nil {
response.Error = err.Error()
return response
}
fileInfo, err := diskFile.Stat()
if err != nil {
response.Error = err.Error()
return response
}
fileContents := make([]byte, fileInfo.Size())
_, err = diskFile.Read(fileContents)
if err != nil {
response.Error = err.Error()
return response
}
response.Content = fileContents
response.Success = true
return response
}
func processMythicRPCPayloadGetContent(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCPayloadGetContentMessage{}
responseMsg := MythicRPCPayloadGetContentMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCPayloadGetContent(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCPayloadGetContent(incomingMessage, authContext)
}
@@ -4,6 +4,7 @@ import (
"database/sql"
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -25,31 +26,33 @@ func init() {
Queue: MYTHIC_RPC_PAYLOAD_REMOVE_COMMAND,
RoutingKey: MYTHIC_RPC_PAYLOAD_REMOVE_COMMAND,
Handler: processMythicRPCPayloadRemoveCommand,
Scopes: []string{mythicjwt.SCOPE_PAYLOAD_WRITE},
})
}
// Endpoint: MYTHIC_RPC_PAYLOAD_REMOVE_COMMAND
func MythicRPCPayloadRemoveCommand(input MythicRPCPayloadRemoveCommandMessage) MythicRPCPayloadRemoveCommandMessageResponse {
func MythicRPCPayloadRemoveCommand(input MythicRPCPayloadRemoveCommandMessage, authContext RabbitMQAuthContext) MythicRPCPayloadRemoveCommandMessageResponse {
response := MythicRPCPayloadRemoveCommandMessageResponse{
Success: false,
}
payload := databaseStructs.Payload{}
if err := database.DB.Get(&payload, `SELECT payload.id, payload.payload_type_id
err := database.DB.Get(&payload, `SELECT
payload.id, payload.payload_type_id
FROM payload
WHERE uuid=$1`, input.PayloadUUID); err != nil {
WHERE uuid=$1 AND operation_id=$2`, input.PayloadUUID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch callback in MythicRPCPayloadRemoveCommand")
response.Error = err.Error()
return response
} else {
if err := PayloadRemoveCommand(payload.ID, payload.PayloadTypeID, input.Commands); err != nil {
logging.LogError(err, "Failed to remove commands in payload")
response.Error = err.Error()
return response
} else {
response.Success = true
return response
}
}
err = PayloadRemoveCommand(payload.ID, payload.PayloadTypeID, input.Commands)
if err != nil {
logging.LogError(err, "Failed to remove commands in payload")
response.Error = err.Error()
return response
}
response.Success = true
return response
}
func PayloadRemoveCommand(PayloadID int, payloadtypeID int, commands []string) error {
for _, command := range commands {
@@ -88,11 +91,16 @@ func processMythicRPCPayloadRemoveCommand(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCPayloadRemoveCommandMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCPayloadRemoveCommand(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCPayloadRemoveCommand(incomingMessage, authContext)
}
@@ -3,6 +3,7 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -11,7 +12,6 @@ import (
)
type MythicRPCPayloadSearchMessage struct {
CallbackID int `json:"callback_id"`
PayloadUUID string `json:"uuid"`
Description string `json:"description"`
Filename string `json:"filename"`
@@ -38,51 +38,31 @@ func init() {
Queue: MYTHIC_RPC_PAYLOAD_SEARCH, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_PAYLOAD_SEARCH, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCPayloadSearch, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_PAYLOAD_READ},
})
}
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCPayloadSearch(input MythicRPCPayloadSearchMessage) MythicRPCPayloadSearchMessageResponse {
func MythicRPCPayloadSearch(input MythicRPCPayloadSearchMessage, authContext RabbitMQAuthContext) MythicRPCPayloadSearchMessageResponse {
response := MythicRPCPayloadSearchMessageResponse{
Success: false,
}
searching := true
if input.CallbackID == 0 && input.Description == "" && input.Filename == "" && len(input.PayloadTypes) == 0 && len(input.BuildParameters) == 0 {
if input.Description == "" && input.Filename == "" && len(input.PayloadTypes) == 0 && len(input.BuildParameters) == 0 {
searching = false
}
if input.PayloadUUID != "" && !searching {
if config, err := getPayloadConfigFromUUID(input.PayloadUUID); err != nil {
response.Error = err.Error()
return response
} else {
response.PayloadConfigurations = append(response.PayloadConfigurations, config)
response.Success = true
return response
}
}
// search payloads based on the supplied information
operationId := 0
payloads := []databaseStructs.Payload{}
if input.CallbackID > 0 {
callback := databaseStructs.Callback{}
err := database.DB.Get(&callback, `SELECT operation_id FROM callback WHERE id=$1`, input.CallbackID)
config, err := getPayloadConfigFromUUID(input.PayloadUUID, authContext.OperationID)
if err != nil {
response.Error = err.Error()
return response
}
operationId = callback.OperationID
} else if input.PayloadUUID != "" {
payload := databaseStructs.Payload{}
err := database.DB.Get(&payload, `SELECT operation_id FROM payload WHERE uuid=$1`, input.PayloadUUID)
if err != nil {
response.Error = err.Error()
return response
}
operationId = payload.OperationID
} else {
response.Error = "Must supply PayloadUUID or CallbackID for searching"
response.PayloadConfigurations = append(response.PayloadConfigurations, config)
response.Success = true
return response
}
// search payloads based on the supplied information
payloads := []databaseStructs.Payload{}
err := database.DB.Select(&payloads, `SELECT
payload.uuid, payload.auto_generated, payload.id,
@@ -92,7 +72,7 @@ func MythicRPCPayloadSearch(input MythicRPCPayloadSearchMessage) MythicRPCPayloa
JOIN payloadtype ON payload.payload_type_id = payloadtype.id
WHERE payload.operation_id=$1 AND payload.deleted=false AND payload.build_phase='success'
ORDER BY payload.id DESC
`, operationId)
`, authContext.OperationID)
if err != nil {
response.Error = err.Error()
return response
@@ -101,9 +81,11 @@ func MythicRPCPayloadSearch(input MythicRPCPayloadSearchMessage) MythicRPCPayloa
for _, payload := range payloads {
if payload.AutoGenerated && !input.IncludeAutoGeneratedPayloads {
continue
} else if len(input.PayloadTypes) > 0 && !utils.SliceContains(input.PayloadTypes, payload.Payloadtype.Name) {
}
if len(input.PayloadTypes) > 0 && !utils.SliceContains(input.PayloadTypes, payload.Payloadtype.Name) {
continue
} else if len(input.BuildParameters) > 0 {
}
if len(input.BuildParameters) > 0 {
allBuildParametersAreGood := true
for _, buildRequirement := range input.BuildParameters {
if buildRequirement.PayloadType == payload.Payloadtype.Name {
@@ -128,21 +110,21 @@ func MythicRPCPayloadSearch(input MythicRPCPayloadSearchMessage) MythicRPCPayloa
}
}
if allBuildParametersAreGood {
if finalPayload, err := getPayloadConfigFromUUID(payload.UuID); err != nil {
finalPayload, err := getPayloadConfigFromUUID(payload.UuID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to get configuration for payload")
response.Error = err.Error()
return response
} else {
if input.Description != "" && finalPayload.Description != input.Description {
continue
}
if input.Filename != "" && finalPayload.Filename != input.Filename {
continue
}
finalPayloads = append(finalPayloads, finalPayload)
}
if input.Description != "" && finalPayload.Description != input.Description {
continue
}
if input.Filename != "" && finalPayload.Filename != input.Filename {
continue
}
finalPayloads = append(finalPayloads, finalPayload)
}
} else if finalPayload, err := getPayloadConfigFromUUID(payload.UuID); err != nil {
} else if finalPayload, err := getPayloadConfigFromUUID(payload.UuID, authContext.OperationID); err != nil {
logging.LogError(err, "Failed to get configuration for payload")
response.Error = err.Error()
return response
@@ -157,7 +139,6 @@ func MythicRPCPayloadSearch(input MythicRPCPayloadSearchMessage) MythicRPCPayloa
}
}
response.PayloadConfigurations = finalPayloads
response.Success = true
return response
}
@@ -166,19 +147,24 @@ func processMythicRPCPayloadSearch(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCPayloadSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCPayloadSearch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCPayloadSearch(incomingMessage, authContext)
}
func getPayloadConfigFromUUID(payloadUUID string) (PayloadConfiguration, error) {
func getPayloadConfigFromUUID(payloadUUID string, operationID int) (PayloadConfiguration, error) {
payloadConfiguration := PayloadConfiguration{}
payload := databaseStructs.Payload{}
if err := database.DB.Get(&payload, `SELECT
err := database.DB.Get(&payload, `SELECT
payload.id, payload.description, payload.uuid, payload.os, payload.wrapped_payload_id, payload.build_phase,
payloadtype.name "payloadtype.name",
filemeta.filename "filemeta.filename",
@@ -188,29 +174,33 @@ func getPayloadConfigFromUUID(payloadUUID string) (PayloadConfiguration, error)
JOIN payloadtype ON payload.payload_type_id = payloadtype.id
JOIN filemeta ON payload.file_id = filemeta.id
WHERE
payload.uuid=$1`, payloadUUID); err != nil {
payload.uuid=$1 AND payload.operation_id=$2`, payloadUUID, operationID)
if err != nil {
logging.LogError(err, "Failed to get payload when searching for payloads")
return payloadConfiguration, err
} else {
payloadConfiguration.Description = payload.Description
payloadConfiguration.SelectedOS = payload.Os
payloadConfiguration.PayloadType = payload.Payloadtype.Name
payloadConfiguration.C2Profiles = GetPayloadC2ProfileInformation(payload)
payloadConfiguration.BuildParameters = GetBuildParameterInformation(payload.ID)
payloadConfiguration.Commands = GetPayloadCommandInformation(payload)
payloadConfiguration.Filename = string(payload.Filemeta.Filename)
payloadConfiguration.AgentFileID = payload.Filemeta.AgentFileID
payloadConfiguration.UUID = payload.UuID
payloadConfiguration.BuildPhase = payload.BuildPhase
if payload.WrappedPayloadID.Valid {
// get the associated UUID for the wrapped payload
wrappedPayload := databaseStructs.Payload{}
if err := database.DB.Get(&wrappedPayload, `SELECT uuid FROM payload WHERE id=$1`, payload.WrappedPayloadID.Int64); err != nil {
logging.LogError(err, "Failed to fetch wrapped payload information")
} else {
payloadConfiguration.WrappedPayloadUUID = wrappedPayload.UuID
}
}
return payloadConfiguration, nil
}
payloadConfiguration.Description = payload.Description
payloadConfiguration.SelectedOS = payload.Os
payloadConfiguration.PayloadType = payload.Payloadtype.Name
payloadConfiguration.C2Profiles = GetPayloadC2ProfileInformation(payload)
payloadConfiguration.BuildParameters = GetBuildParameterInformation(payload.ID)
payloadConfiguration.Commands = GetPayloadCommandInformation(payload)
payloadConfiguration.Filename = string(payload.Filemeta.Filename)
payloadConfiguration.AgentFileID = payload.Filemeta.AgentFileID
payloadConfiguration.UUID = payload.UuID
payloadConfiguration.BuildPhase = payload.BuildPhase
if payload.WrappedPayloadID.Valid {
// get the associated UUID for the wrapped payload
wrappedPayload := databaseStructs.Payload{}
err = database.DB.Get(&wrappedPayload, `SELECT
uuid
FROM payload
WHERE id=$1 AND operation_id=$2`, payload.WrappedPayloadID.Int64, operationID)
if err != nil {
logging.LogError(err, "Failed to fetch wrapped payload information")
} else {
payloadConfiguration.WrappedPayloadUUID = wrappedPayload.UuID
}
}
return payloadConfiguration, nil
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"time"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -31,104 +32,107 @@ func init() {
Queue: MYTHIC_RPC_PAYLOAD_UPDATE_BUILD_STEP, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_PAYLOAD_UPDATE_BUILD_STEP, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCPayloadUpdateBuildStep, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_PAYLOAD_WRITE},
})
}
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCPayloadUpdateBuildStep(input MythicRPCPayloadUpdateBuildStepMessage) MythicRPCPayloadUpdateBuildStepMessageResponse {
func MythicRPCPayloadUpdateBuildStep(input MythicRPCPayloadUpdateBuildStepMessage, authContext RabbitMQAuthContext) MythicRPCPayloadUpdateBuildStepMessageResponse {
response := MythicRPCPayloadUpdateBuildStepMessageResponse{
Success: false,
}
payload := databaseStructs.Payload{}
if err := database.DB.Get(&payload, `SELECT
err := database.DB.Get(&payload, `SELECT
id
FROM payload
WHERE uuid=$1`, input.PayloadUUID); err != nil {
WHERE uuid=$1 AND operation_id=$2`, input.PayloadUUID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to find payload")
response.Error = err.Error()
return response
} else {
// we need to set the endtime for the specified step to now() and also the start time for the next step to now()
stepNow := time.Now().UTC()
allBuildSteps := []databaseStructs.PayloadBuildStep{}
if err := database.DB.Select(&allBuildSteps, `SELECT
}
// we need to set the endtime for the specified step to now() and also the start time for the next step to now()
stepNow := time.Now().UTC()
allBuildSteps := []databaseStructs.PayloadBuildStep{}
err = database.DB.Select(&allBuildSteps, `SELECT
id, step_number, step_name, start_time, end_time
FROM payload_build_step
WHERE payload_id=$1 ORDER BY step_number ASC`, payload.ID); err != nil {
logging.LogError(err, "Failed to fetch current build steps for payload")
} else {
setStartTimeOfNextStep := false
var prevSteps []databaseStructs.PayloadBuildStep
for _, step := range allBuildSteps {
if setStartTimeOfNextStep {
if !step.StartTime.Valid {
setStartTimeOfNextStep = false
step.StartTime.Time = stepNow
step.StartTime.Valid = true
if _, err := database.DB.NamedExec(`UPDATE payload_build_step SET
WHERE payload_id=$1 ORDER BY step_number ASC`, payload.ID)
if err != nil {
logging.LogError(err, "Failed to fetch current build steps for payload")
response.Error = err.Error()
return response
}
setStartTimeOfNextStep := false
var prevSteps []databaseStructs.PayloadBuildStep
for _, step := range allBuildSteps {
if setStartTimeOfNextStep {
if !step.StartTime.Valid {
setStartTimeOfNextStep = false
step.StartTime.Time = stepNow
step.StartTime.Valid = true
_, err = database.DB.NamedExec(`UPDATE payload_build_step SET
start_time=:start_time
WHERE id=:id`, step); err != nil {
logging.LogError(err, "Failed to update payload build step")
response.Error = err.Error()
return response
} else {
// break out early here so that we don't keep adding steps as "previous"
break
}
}
}
if step.StepName == input.StepName {
setStartTimeOfNextStep = true
step.StepStdout = input.StepStdout
step.StepStderr = input.StepStderr
step.Success = input.StepSuccess
step.StepSkip = input.StepSkip
step.EndTime.Valid = true
step.EndTime.Time = stepNow
if !step.StartTime.Valid {
step.StartTime.Valid = true
step.StartTime.Time = stepNow
}
if _, err := database.DB.NamedExec(`UPDATE payload_build_step SET
step_stdout=:step_stdout, step_stderr=:step_stderr,
step_success=:step_success, end_time=:end_time, start_time=:start_time, step_skip=:step_skip
WHERE id=:id`, step); err != nil {
logging.LogError(err, "Failed to update payload build step")
response.Error = err.Error()
return response
}
}
if !step.EndTime.Valid {
prevSteps = append(prevSteps, step)
}
}
for _, step := range prevSteps {
// loop through all the steps that weren't marked as done before this one and update them
if !step.StartTime.Valid {
step.StartTime.Valid = true
step.StartTime.Time = stepNow
}
if !step.EndTime.Valid {
step.EndTime.Valid = true
step.EndTime.Time = stepNow
}
step.StepSkip = true
step.Success = input.StepSuccess
step.StepStdout = step.StepStdout + "\nAutomatically marked as done due to future step completing"
if _, err := database.DB.NamedExec(`UPDATE payload_build_step SET
step_stdout=:step_stdout, step_success=:step_success, end_time=:end_time, start_time=:start_time,
step_skip=:step_skip
WHERE id=:id`, step); err != nil {
WHERE id=:id`, step)
if err != nil {
logging.LogError(err, "Failed to update payload build step")
response.Error = err.Error()
return response
}
// break out early here so that we don't keep adding steps as "previous"
break
}
}
if step.StepName == input.StepName {
setStartTimeOfNextStep = true
step.StepStdout = input.StepStdout
step.StepStderr = input.StepStderr
step.Success = input.StepSuccess
step.StepSkip = input.StepSkip
step.EndTime.Valid = true
step.EndTime.Time = stepNow
if !step.StartTime.Valid {
step.StartTime.Valid = true
step.StartTime.Time = stepNow
}
_, err = database.DB.NamedExec(`UPDATE payload_build_step SET
step_stdout=:step_stdout, step_stderr=:step_stderr,
step_success=:step_success, end_time=:end_time, start_time=:start_time, step_skip=:step_skip
WHERE id=:id`, step)
if err != nil {
logging.LogError(err, "Failed to update payload build step")
response.Error = err.Error()
return response
}
}
if !step.EndTime.Valid {
prevSteps = append(prevSteps, step)
}
}
for _, step := range prevSteps {
// loop through all the steps that weren't marked as done before this one and update them
if !step.StartTime.Valid {
step.StartTime.Valid = true
step.StartTime.Time = stepNow
}
if !step.EndTime.Valid {
step.EndTime.Valid = true
step.EndTime.Time = stepNow
}
step.StepSkip = true
step.Success = input.StepSuccess
step.StepStdout = step.StepStdout + "\nAutomatically marked as done due to future step completing"
_, err = database.DB.NamedExec(`UPDATE payload_build_step SET
step_stdout=:step_stdout, step_success=:step_success, end_time=:end_time, start_time=:start_time,
step_skip=:step_skip
WHERE id=:id`, step)
if err != nil {
logging.LogError(err, "Failed to update payload build step")
response.Error = err.Error()
return response
}
}
return response
}
func processMythicRPCPayloadUpdateBuildStep(msg amqp.Delivery) interface{} {
@@ -136,11 +140,16 @@ func processMythicRPCPayloadUpdateBuildStep(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCPayloadUpdateBuildStepMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCPayloadUpdateBuildStep(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCPayloadUpdateBuildStep(incomingMessage, authContext)
}
@@ -3,11 +3,13 @@ package rabbitmq
import (
"encoding/json"
"fmt"
"strings"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
amqp "github.com/rabbitmq/amqp091-go"
"strings"
)
type MythicRPCPayloadOnHostCreateMessage struct {
@@ -30,70 +32,91 @@ func init() {
Queue: MYTHIC_RPC_PAYLOADONHOST_CREATE,
RoutingKey: MYTHIC_RPC_PAYLOADONHOST_CREATE,
Handler: processMythicRPCPayloadOnHostCreate,
Scopes: []string{mythicjwt.SCOPE_PAYLOAD_WRITE},
})
}
// Endpoint: MYTHIC_RPC_PAYLOADONHOST_CREATE
func MythicRPCPayloadOnHostCreate(input MythicRPCPayloadOnHostCreateMessage) MythicRPCPayloadOnHostCreateMessageResponse {
func MythicRPCPayloadOnHostCreate(input MythicRPCPayloadOnHostCreateMessage, authContext RabbitMQAuthContext) MythicRPCPayloadOnHostCreateMessageResponse {
response := MythicRPCPayloadOnHostCreateMessageResponse{
Success: false,
}
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
err := database.DB.Get(&task, `SELECT
callback.operation_id "callback.operation_id",
callback.host "callback.host"
FROM task
JOIN callback ON task.callback_id = callback.id
WHERE task.id = $1`, input.TaskID); err != nil {
WHERE task.id = $1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch task")
response.Error = err.Error()
return response
}
if input.PayloadOnHost.Host == "" {
input.PayloadOnHost.Host = task.Callback.Host
} else {
if input.PayloadOnHost.Host == "" {
input.PayloadOnHost.Host = task.Callback.Host
} else {
input.PayloadOnHost.Host = strings.ToUpper(input.PayloadOnHost.Host)
}
payloadId := 0
if input.PayloadOnHost.PayloadId != nil {
payloadId = *input.PayloadOnHost.PayloadId
} else if input.PayloadOnHost.PayloadUUID != nil {
payload := databaseStructs.Payload{}
if err := database.DB.Get(&payload, `SELECT id FROM payload WHERE uuid=$1 AND operation_id=$2`,
*input.PayloadOnHost.PayloadUUID, task.OperationID); err != nil {
logging.LogError(err, "Failed to find specified payload UUID")
response.Error = err.Error()
return response
} else {
payloadId = payload.ID
}
}
if payloadId == 0 {
response.Error = fmt.Sprintf("Failed to find the specified payload")
return response
} else if _, err := database.DB.Exec(`INSERT INTO payloadonhost
(host, payload_id, operation_id, task_id) VALUES
($1, $2, $3, $4)`,
input.PayloadOnHost.Host, payloadId, task.OperationID, task.ID); err != nil {
logging.LogError(err, "Failed to create new payload on host value")
input.PayloadOnHost.Host = strings.ToUpper(input.PayloadOnHost.Host)
}
payloadId := 0
if input.PayloadOnHost.PayloadId != nil {
payload := databaseStructs.Payload{}
err = database.DB.Get(&payload, `SELECT
id
FROM payload
WHERE id=$1 AND operation_id=$2`,
*input.PayloadOnHost.PayloadId, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to find specified payload UUID")
response.Error = err.Error()
return response
} else {
response.Success = true
}
payloadId = payload.ID
} else if input.PayloadOnHost.PayloadUUID != nil {
payload := databaseStructs.Payload{}
err = database.DB.Get(&payload, `SELECT
id
FROM payload
WHERE uuid=$1 AND operation_id=$2`,
*input.PayloadOnHost.PayloadUUID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to find specified payload UUID")
response.Error = err.Error()
return response
}
payloadId = payload.ID
}
if payloadId == 0 {
response.Error = fmt.Sprintf("Failed to find the specified payload")
return response
}
_, err = database.DB.Exec(`INSERT INTO payloadonhost
(host, payload_id, operation_id, task_id) VALUES
($1, $2, $3, $4)`,
input.PayloadOnHost.Host, payloadId, authContext.OperationID, task.ID)
if err != nil {
logging.LogError(err, "Failed to create new payload on host value")
response.Error = err.Error()
return response
}
response.Success = true
return response
}
func processMythicRPCPayloadOnHostCreate(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCPayloadOnHostCreateMessage{}
responseMsg := MythicRPCPayloadOnHostCreateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCPayloadOnHostCreate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCPayloadOnHostCreate(incomingMessage, authContext)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -24,11 +26,12 @@ func init() {
Queue: MYTHIC_RPC_PROCESS_CREATE,
RoutingKey: MYTHIC_RPC_PROCESS_CREATE,
Handler: processMythicRPCProcessCreate,
Scopes: []string{mythicjwt.SCOPE_BROWSER_WRITE},
})
}
// Endpoint: MYTHIC_RPC_PROCESS_CREATE
func MythicRPCProcessCreate(input MythicRPCProcessCreateMessage) MythicRPCProcessCreateMessageResponse {
func MythicRPCProcessCreate(input MythicRPCProcessCreateMessage, authContext RabbitMQAuthContext) MythicRPCProcessCreateMessageResponse {
response := MythicRPCProcessCreateMessageResponse{
Success: false,
}
@@ -37,7 +40,7 @@ func MythicRPCProcessCreate(input MythicRPCProcessCreateMessage) MythicRPCProces
return response
}
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
err := database.DB.Get(&task, `SELECT
task.id, task.status, task.completed, task.status_timestamp_processed, task.operator_id, task.operation_id,
task.apitokens_id, task.eventstepinstance_id,
callback.host "callback.host",
@@ -49,29 +52,36 @@ func MythicRPCProcessCreate(input MythicRPCProcessCreateMessage) MythicRPCProces
FROM task
JOIN callback ON task.callback_id = callback.id
JOIN payload ON callback.registered_payload_id = payload.id
WHERE task.id = $1`, input.TaskID); err != nil {
WHERE task.id = $1 AND task.operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch task")
response.Error = err.Error()
return response
} else if err := HandleAgentMessagePostResponseProcesses(task, &input.Processes, int(task.APITokensID.Int64)); err != nil {
}
err = HandleAgentMessagePostResponseProcesses(task, &input.Processes, int(task.APITokensID.Int64))
if err != nil {
logging.LogError(err, "Failed to create processes in MythicRPCProcessCreate")
response.Error = err.Error()
return response
} else {
response.Success = true
return response
}
response.Success = true
return response
}
func processMythicRPCProcessCreate(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCProcessCreateMessage{}
responseMsg := MythicRPCProcessCreateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCProcessCreate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCProcessCreate(incomingMessage, authContext)
}
@@ -3,6 +3,8 @@ package rabbitmq
import (
"encoding/json"
"fmt"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/mitchellh/mapstructure"
"github.com/its-a-feature/Mythic/database"
@@ -12,7 +14,6 @@ import (
)
type MythicRPCProcessSearchMessage struct {
TaskID int `json:"task_id"` //required
SearchProcess MythicRPCProcessSearchProcessData `json:"process"`
}
type MythicRPCProcessSearchMessageResponse struct {
@@ -40,108 +41,105 @@ func init() {
Queue: MYTHIC_RPC_PROCESS_SEARCH,
RoutingKey: MYTHIC_RPC_PROCESS_SEARCH,
Handler: processMythicRPCProcessSearch,
Scopes: []string{mythicjwt.SCOPE_BROWSER_READ},
})
}
// Endpoint: MYTHIC_RPC_PROCESS_SEARCH
func MythicRPCProcessSearch(input MythicRPCProcessSearchMessage) MythicRPCProcessSearchMessageResponse {
func MythicRPCProcessSearch(input MythicRPCProcessSearchMessage, authContext RabbitMQAuthContext) MythicRPCProcessSearchMessageResponse {
response := MythicRPCProcessSearchMessageResponse{
Success: false,
Processes: []MythicRPCProcessSearchProcessData{},
}
paramDict := make(map[string]interface{})
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
task.id, task.operation_id
FROM task
WHERE task.id=$1`, input.TaskID); err != nil {
paramDict["operation_id"] = authContext.OperationID
searchString := `SELECT * FROM mythictree WHERE operation_id=:operation_id AND tree_type='process' `
if input.SearchProcess.Host != nil {
paramDict["host"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.Host)
searchString += "AND host ILIKE :host "
}
if input.SearchProcess.ProcessID != nil {
paramDict["process_id"] = fmt.Sprintf("%d", *input.SearchProcess.ProcessID)
searchString += "AND metadata->>'process_id'=:process_id "
}
if input.SearchProcess.Architecture != nil {
paramDict["architecture"] = *input.SearchProcess.Architecture
searchString += "AND metadata->>'architecture'=:architecture "
}
if input.SearchProcess.ParentProcessID != nil {
paramDict["parent_process_id"] = fmt.Sprintf("%d", *input.SearchProcess.ParentProcessID)
searchString += "AND metadata->>'parent_process_id'=:parent_process_id "
}
if input.SearchProcess.BinPath != nil {
paramDict["bin_path"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.BinPath)
searchString += "AND metadata->>'bin_path' ILIKE :bin_path "
}
if input.SearchProcess.Name != nil {
paramDict["name"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.Name)
searchString += "AND name ILIKE :name "
}
if input.SearchProcess.User != nil {
paramDict["user"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.User)
searchString += "AND metadata->>'user' ILIKE :user "
}
if input.SearchProcess.CommandLine != nil {
paramDict["command_line"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.CommandLine)
searchString += "AND metadata->>'command_line' ILIKE :command_line "
}
if input.SearchProcess.IntegrityLevel != nil {
paramDict["integrity_level"] = fmt.Sprintf("%d", *input.SearchProcess.IntegrityLevel)
searchString += "AND metadata->>'integrity_level'=:integrity_level "
}
if input.SearchProcess.Description != nil {
paramDict["description"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.Description)
searchString += "AND metadata->>'description' ILIKE :description "
}
if input.SearchProcess.Signer != nil {
paramDict["signer"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.Signer)
searchString += "AND metadata->>'signer' ILIKE :signer "
}
searchString += " ORDER BY id DESC"
rows, err := database.DB.NamedQuery(searchString, paramDict)
if err != nil {
logging.LogError(err, "Failed to search artifact information")
response.Error = err.Error()
return response
} else {
paramDict["operation_id"] = task.OperationID
searchString := `SELECT * FROM mythictree WHERE operation_id=:operation_id AND tree_type='process' `
if input.SearchProcess.Host != nil {
paramDict["host"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.Host)
searchString += "AND host ILIKE :host "
}
if input.SearchProcess.ProcessID != nil {
paramDict["process_id"] = fmt.Sprintf("%d", *input.SearchProcess.ProcessID)
searchString += "AND metadata->>'process_id'=:process_id "
}
if input.SearchProcess.Architecture != nil {
paramDict["architecture"] = *input.SearchProcess.Architecture
searchString += "AND metadata->>'architecture'=:architecture "
}
if input.SearchProcess.ParentProcessID != nil {
paramDict["parent_process_id"] = fmt.Sprintf("%d", *input.SearchProcess.ParentProcessID)
searchString += "AND metadata->>'parent_process_id'=:parent_process_id "
}
if input.SearchProcess.BinPath != nil {
paramDict["bin_path"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.BinPath)
searchString += "AND metadata->>'bin_path' ILIKE :bin_path "
}
if input.SearchProcess.Name != nil {
paramDict["name"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.Name)
searchString += "AND name ILIKE :name "
}
if input.SearchProcess.User != nil {
paramDict["user"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.User)
searchString += "AND metadata->>'user' ILIKE :user "
}
if input.SearchProcess.CommandLine != nil {
paramDict["command_line"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.CommandLine)
searchString += "AND metadata->>'command_line' ILIKE :command_line "
}
if input.SearchProcess.IntegrityLevel != nil {
paramDict["integrity_level"] = fmt.Sprintf("%d", *input.SearchProcess.IntegrityLevel)
searchString += "AND metadata->>'integrity_level'=:integrity_level "
}
if input.SearchProcess.Description != nil {
paramDict["description"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.Description)
searchString += "AND metadata->>'description' ILIKE :description "
}
if input.SearchProcess.Signer != nil {
paramDict["signer"] = fmt.Sprintf("%%%s%%", *input.SearchProcess.Signer)
searchString += "AND metadata->>'signer' ILIKE :signer "
}
searchString += " ORDER BY id DESC"
if rows, err := database.DB.NamedQuery(searchString, paramDict); err != nil {
logging.LogError(err, "Failed to search artifact information")
response.Error = err.Error()
return response
}
defer rows.Close()
for rows.Next() {
searchResult := databaseStructs.MythicTree{}
if err = rows.StructScan(&searchResult); err != nil {
logging.LogError(err, "Failed to get row from artifacts for search")
} else {
defer rows.Close()
for rows.Next() {
searchResult := databaseStructs.MythicTree{}
if err = rows.StructScan(&searchResult); err != nil {
logging.LogError(err, "Failed to get row from artifacts for search")
} else {
returnedProcess := MythicRPCProcessSearchProcessData{}
if err := mapstructure.Decode(searchResult.Metadata.StructValue(), &returnedProcess); err != nil {
logging.LogError(err, "Failed to decode process search result to struct")
} else {
returnedProcess.Host = &searchResult.Host
nm := string(searchResult.Name)
returnedProcess.Name = &nm
response.Processes = append(response.Processes, returnedProcess)
}
}
returnedProcess := MythicRPCProcessSearchProcessData{}
if err := mapstructure.Decode(searchResult.Metadata.StructValue(), &returnedProcess); err != nil {
logging.LogError(err, "Failed to decode process search result to struct")
} else {
returnedProcess.Host = &searchResult.Host
nm := string(searchResult.Name)
returnedProcess.Name = &nm
response.Processes = append(response.Processes, returnedProcess)
}
response.Success = true
return response
}
}
response.Success = true
return response
}
func processMythicRPCProcessSearch(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCProcessSearchMessage{}
responseMsg := MythicRPCProcessSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCProcessSearch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCProcessSearch(incomingMessage, authContext)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -29,13 +31,14 @@ func init() {
Queue: MYTHIC_RPC_PROXY_START,
RoutingKey: MYTHIC_RPC_PROXY_START,
Handler: processMythicRPCProxyStart,
Scopes: []string{mythicjwt.SCOPE_TASK_WRITE},
})
}
// Endpoint: MYTHIC_RPC_PROXY
//
// Creates a FileMeta object for a specific task in Mythic's database and writes contents to disk with a random UUID filename.
func MythicRPCProxyStart(input MythicRPCProxyStartMessage) MythicRPCProxyStartMessageResponse {
func MythicRPCProxyStart(input MythicRPCProxyStartMessage, authContext RabbitMQAuthContext) MythicRPCProxyStartMessageResponse {
response := MythicRPCProxyStartMessageResponse{
Success: false,
}
@@ -44,7 +47,10 @@ func MythicRPCProxyStart(input MythicRPCProxyStartMessage) MythicRPCProxyStartMe
return response
}
task := databaseStructs.Task{ID: input.TaskID}
err := database.DB.Get(&task, `SELECT id, operation_id, callback_id FROM task WHERE id=$1`, task.ID)
err := database.DB.Get(&task, `
SELECT id, operation_id, callback_id
FROM task
WHERE id=$1 AND operation_id=$2`, task.ID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to get task from database to start socks")
response.Error = err.Error()
@@ -89,11 +95,16 @@ func processMythicRPCProxyStart(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCProxyStartMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCProxyStart(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCProxyStart(incomingMessage, authContext)
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -29,67 +30,74 @@ func init() {
Queue: MYTHIC_RPC_PROXY_STOP,
RoutingKey: MYTHIC_RPC_PROXY_STOP,
Handler: processMythicRPCProxyStop,
Scopes: []string{mythicjwt.SCOPE_TASK_WRITE},
})
}
// Endpoint: MYTHIC_RPC_PROXY
//
// Creates a FileMeta object for a specific task in Mythic's database and writes contents to disk with a random UUID filename.
func MythicRPCProxyStop(input MythicRPCProxyStopMessage) MythicRPCProxyStopMessageResponse {
func MythicRPCProxyStop(input MythicRPCProxyStopMessage, authContext RabbitMQAuthContext) MythicRPCProxyStopMessageResponse {
response := MythicRPCProxyStopMessageResponse{
Success: false,
}
task := databaseStructs.Task{ID: input.TaskID}
if err := database.DB.Get(&task, `SELECT id, operation_id, callback_id FROM task WHERE id=$1`, task.ID); err != nil {
err := database.DB.Get(&task, `SELECT
id, operation_id, callback_id
FROM task
WHERE id=$1 AND operation_id=$2`, task.ID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to get task from database to start socks")
response.Error = err.Error()
return response
} else {
switch input.PortType {
case CALLBACK_PORT_TYPE_RPORTFWD:
fallthrough
case CALLBACK_PORT_TYPE_SOCKS:
fallthrough
case CALLBACK_PORT_TYPE_INTERACTIVE:
}
switch input.PortType {
case CALLBACK_PORT_TYPE_RPORTFWD:
fallthrough
case CALLBACK_PORT_TYPE_SOCKS:
fallthrough
case CALLBACK_PORT_TYPE_INTERACTIVE:
if input.Port == 0 {
// lookup the port that might need to be closed for this PortType and CallbackID
input.Port = proxyPorts.GetPortForTypeAndCallback(task.ID, task.CallbackID, input.PortType)
if input.Port == 0 {
// lookup the port that might need to be closed for this PortType and CallbackID
input.Port = proxyPorts.GetPortForTypeAndCallback(task.ID, task.CallbackID, input.PortType)
if input.Port == 0 {
response.Error = fmt.Sprintf("Failed to find port for type, %s, and task, %d", input.PortType, task.ID)
return response
}
response.Error = fmt.Sprintf("Failed to find port for type, %s, and task, %d", input.PortType, task.ID)
return response
}
}
response.LocalPort = input.Port
err := proxyPorts.Remove(task.CallbackID,
input.PortType,
input.Port,
task.OperationID,
"",
0,
input.Username,
input.Password)
if err != nil {
logging.LogError(err, "Failed to stop callback port")
response.Error = err.Error()
return response
} else {
response.Success = true
return response
}
}
response.LocalPort = input.Port
err = proxyPorts.Remove(task.CallbackID,
input.PortType,
input.Port,
task.OperationID,
"",
0,
input.Username,
input.Password)
if err != nil {
logging.LogError(err, "Failed to stop callback port")
response.Error = err.Error()
return response
}
response.Success = true
return response
}
func processMythicRPCProxyStop(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCProxyStopMessage{}
responseMsg := MythicRPCProxyStopMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCProxyStop(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCProxyStop(incomingMessage, authContext)
}
@@ -4,6 +4,7 @@ import (
"encoding/json"
"time"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -25,13 +26,14 @@ func init() {
Queue: MYTHIC_RPC_RESPONSE_CREATE,
RoutingKey: MYTHIC_RPC_RESPONSE_CREATE,
Handler: processMythicRPCPayloadResponseCreate,
Scopes: []string{mythicjwt.SCOPE_RESPONSE_WRITE},
})
}
// Endpoint: MYTHIC_RPC_RESPONSE_CREATE
//
// Creates a FileMeta object for a specific task in Mythic's database and writes contents to disk with a random UUID filename.
func MythicRPCResponseCreate(input MythicRPCResponseCreateMessage) MythicRPCResponseCreateMessageResponse {
func MythicRPCResponseCreate(input MythicRPCResponseCreateMessage, authContext RabbitMQAuthContext) MythicRPCResponseCreateMessageResponse {
response := MythicRPCResponseCreateMessageResponse{
Success: false,
}
@@ -42,7 +44,10 @@ func MythicRPCResponseCreate(input MythicRPCResponseCreateMessage) MythicRPCResp
response.Error = "Response must have actual bytes"
return response
}
err := database.DB.Get(&task, `SELECT operation_id, agent_task_id FROM task WHERE id=$1`, input.TaskID)
err := database.DB.Get(&task, `SELECT
operation_id, agent_task_id, eventstepinstance_id, apitokens_id
FROM task
WHERE id=$1 AND operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "failed to fetch task from database")
response.Error = err.Error()
@@ -63,11 +68,16 @@ func processMythicRPCPayloadResponseCreate(msg amqp.Delivery) interface{} {
responseMsg := MythicRPCResponseCreateMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCResponseCreate(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCResponseCreate(incomingMessage, authContext)
}
@@ -3,6 +3,7 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -30,13 +31,14 @@ func init() {
Queue: MYTHIC_RPC_RESPONSE_SEARCH,
RoutingKey: MYTHIC_RPC_RESPONSE_SEARCH,
Handler: processMythicRPCPayloadResponseSearch,
Scopes: []string{mythicjwt.SCOPE_RESPONSE_READ},
})
}
// Endpoint: MYTHIC_RPC_RESPONSE_SEARCH
//
// Creates a FileMeta object for a specific task in Mythic's database and writes contents to disk with a random UUID filename.
func MythicRPCResponseSearch(input MythicRPCResponseSearchMessage) MythicRPCResponseSearchMessageResponse {
func MythicRPCResponseSearch(input MythicRPCResponseSearchMessage, authContext RabbitMQAuthContext) MythicRPCResponseSearchMessageResponse {
response := MythicRPCResponseSearchMessageResponse{
Success: false,
}
@@ -48,43 +50,50 @@ func MythicRPCResponseSearch(input MythicRPCResponseSearchMessage) MythicRPCResp
responseSearch = "%" + responseSearch + "%"
}
task := databaseStructs.Task{}
if err := database.DB.Get(&task, `SELECT
err := database.DB.Get(&task, `SELECT
operation_id, display_id, id
FROM task
WHERE id=$1`, input.TaskID); err != nil {
WHERE id=$1 AND operation_id=$2`, input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(err, "Failed to fetch task for MythicRPCResponseSearch")
response.Error = err.Error()
return response
} else if err := database.DB.Select(&results, `SELECT
id, response, task_id
FROM response
WHERE operation_id=$1 AND response LIKE $2 AND task_id=$3 ORDER BY id DESC`,
task.OperationID, responseSearch, input.TaskID); err != nil {
}
err = database.DB.Select(&results, `SELECT
id, response, task_id
FROM response
WHERE operation_id=$1 AND response LIKE $2 AND task_id=$3 ORDER BY id DESC`,
authContext.OperationID, responseSearch, input.TaskID)
if err != nil {
logging.LogError(err, "Failed to fetch responses for MythicRPCResponseSearch")
response.Error = err.Error()
return response
} else {
for _, resp := range results {
response.Responses = append(response.Responses, MythicRPCResponse{
ResponseID: resp.ID,
Response: resp.Response,
TaskID: resp.TaskID,
})
}
response.Success = true
return response
}
for _, resp := range results {
response.Responses = append(response.Responses, MythicRPCResponse{
ResponseID: resp.ID,
Response: resp.Response,
TaskID: resp.TaskID,
})
}
response.Success = true
return response
}
func processMythicRPCPayloadResponseSearch(msg amqp.Delivery) interface{} {
incomingMessage := MythicRPCResponseSearchMessage{}
responseMsg := MythicRPCResponseSearchMessageResponse{
Success: false,
}
if err := json.Unmarshal(msg.Body, &incomingMessage); err != nil {
err := json.Unmarshal(msg.Body, &incomingMessage)
if err != nil {
logging.LogError(err, "Failed to unmarshal JSON into struct")
responseMsg.Error = err.Error()
} else {
return MythicRPCResponseSearch(incomingMessage)
return responseMsg
}
return responseMsg
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCResponseSearch(incomingMessage, authContext)
}
@@ -2,6 +2,8 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/eventing"
@@ -33,11 +35,12 @@ func init() {
Queue: MYTHIC_RPC_TAG_CREATE, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_TAG_CREATE, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCTagCreate, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_TAG_WRITE},
})
}
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCTagCreate(input MythicRPCTagCreateMessage) MythicRPCTagCreateMessageResponse {
func MythicRPCTagCreate(input MythicRPCTagCreateMessage, authContext RabbitMQAuthContext) MythicRPCTagCreateMessageResponse {
response := MythicRPCTagCreateMessageResponse{
Success: false,
}
@@ -46,19 +49,12 @@ func MythicRPCTagCreate(input MythicRPCTagCreateMessage) MythicRPCTagCreateMessa
URL: input.URL,
Source: input.Source,
Data: GetMythicJSONTextFromStruct(input.Data),
Operation: authContext.OperationID,
}
operationID := 0
err := database.DB.Get(&operationID, "SELECT operation_id FROM tagtype WHERE id = $1", tag.TagTypeID)
if err != nil {
response.Success = false
response.Error = err.Error()
return response
}
tag.Operation = operationID
if input.MythicTreeID != nil {
mythicTree := databaseStructs.MythicTree{}
err = database.DB.Get(&mythicTree, `SELECT id FROM mythictree WHERE id=$1 AND operation_id=$2`,
*input.MythicTreeID, operationID)
err := database.DB.Get(&mythicTree, `SELECT id FROM mythictree WHERE id=$1 AND operation_id=$2`,
*input.MythicTreeID, authContext.OperationID)
if err != nil {
logging.LogError(nil, "Failed to get mythictree info")
response.Success = false
@@ -70,8 +66,8 @@ func MythicRPCTagCreate(input MythicRPCTagCreateMessage) MythicRPCTagCreateMessa
}
if input.FileID != nil {
fileMeta := databaseStructs.Filemeta{}
err = database.DB.Get(&fileMeta, `SELECT id FROM filemeta WHERE id=$1 AND operation_id=$2`,
*input.FileID, operationID)
err := database.DB.Get(&fileMeta, `SELECT id FROM filemeta WHERE id=$1 AND operation_id=$2`,
*input.FileID, authContext.OperationID)
if err != nil {
logging.LogError(nil, "Failed to get filemeta info")
response.Success = false
@@ -83,8 +79,8 @@ func MythicRPCTagCreate(input MythicRPCTagCreateMessage) MythicRPCTagCreateMessa
}
if input.TaskID != nil {
task := databaseStructs.Task{}
err = database.DB.Get(&task, `SELECT id FROM task WHERE id=$1 AND operation_id=$2`,
*input.TaskID, operationID)
err := database.DB.Get(&task, `SELECT id FROM task WHERE id=$1 AND operation_id=$2`,
*input.TaskID, authContext.OperationID)
if err != nil {
logging.LogError(nil, "Failed to get task info")
response.Success = false
@@ -96,8 +92,8 @@ func MythicRPCTagCreate(input MythicRPCTagCreateMessage) MythicRPCTagCreateMessa
}
if input.CredentialID != nil {
credential := databaseStructs.Credential{}
err = database.DB.Get(&credential, `SELECT id FROM credential WHERE id=$1 AND operation_id=$2`,
*input.CredentialID, operationID)
err := database.DB.Get(&credential, `SELECT id FROM credential WHERE id=$1 AND operation_id=$2`,
*input.CredentialID, authContext.OperationID)
if err != nil {
logging.LogError(nil, "Failed to get credential info")
response.Success = false
@@ -107,10 +103,18 @@ func MythicRPCTagCreate(input MythicRPCTagCreateMessage) MythicRPCTagCreateMessa
tag.Credential.Valid = true
tag.Credential.Int64 = int64(credential.ID)
}
if authContext.APITokensID > 0 {
tag.APITokensID.Valid = true
tag.APITokensID.Int64 = int64(authContext.APITokensID)
}
if authContext.EventStepInstanceID > 0 {
tag.EventStepInstanceID.Valid = true
tag.EventStepInstanceID.Int64 = int64(authContext.EventStepInstanceID)
}
statement, err := database.DB.PrepareNamed(`INSERT INTO tag
(operation_id, data, url, source, tagtype_id, mythictree_id, filemeta_id, task_id, response_id, credential_id, keylog_id, taskartifact_id)
(operation_id, data, url, source, tagtype_id, mythictree_id, filemeta_id, task_id, response_id, credential_id, keylog_id, taskartifact_id, apitokens_id, eventstepinstance_id)
VALUES
(:operation_id, :data, :url, :source, :tagtype_id, :mythictree_id, :filemeta_id, :task_id, :response_id, :credential_id, :keylog_id, :taskartifact_id)
(:operation_id, :data, :url, :source, :tagtype_id, :mythictree_id, :filemeta_id, :task_id, :response_id, :credential_id, :keylog_id, :taskartifact_id, :apitokens_id, :eventstepinstance_id)
RETURNING id`)
if err != nil {
logging.LogError(nil, "Failed to prepare statement for adding tag")
@@ -147,5 +151,10 @@ func processMythicRPCTagCreate(msg amqp.Delivery) interface{} {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCTagCreate(incomingMessage)
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCTagCreate(incomingMessage, authContext)
}
@@ -3,6 +3,7 @@ package rabbitmq
import (
"encoding/json"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
@@ -11,7 +12,6 @@ import (
)
type MythicRPCTagSearchMessage struct {
TaskID int `json:"task_id"`
SearchTagID *int `json:"search_tag_id"`
SearchTagTaskID *int `json:"search_tag_task_id"`
SearchTagFileID *int `json:"search_tag_file_id,omitempty"`
@@ -55,20 +55,15 @@ func init() {
Queue: MYTHIC_RPC_TAG_SEARCH, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_TAG_SEARCH, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCTagSearch, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_TAG_READ},
})
}
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCTagSearch(input MythicRPCTagSearchMessage) MythicRPCTagSearchMessageResponse {
func MythicRPCTagSearch(input MythicRPCTagSearchMessage, authContext RabbitMQAuthContext) MythicRPCTagSearchMessageResponse {
response := MythicRPCTagSearchMessageResponse{
Success: false,
}
operationID := 0
err := database.DB.Get(&operationID, `SELECT task.operation_id FROM task WHERE id=$1`, input.TaskID)
if err != nil {
response.Error = err.Error()
return response
}
paramDict := make(map[string]interface{})
setAnySearchValues := false
searchString := `SELECT
@@ -154,11 +149,10 @@ func MythicRPCTagSearch(input MythicRPCTagSearchMessage) MythicRPCTagSearchMessa
}
if !setAnySearchValues {
searchString += `WHERE tag.operation_id=:operation_id `
paramDict["operation_id"] = operationID
} else {
searchString += `AND tag.operation_id=:operation_id `
paramDict["operation_id"] = operationID
}
paramDict["operation_id"] = authContext.OperationID
searchString += " ORDER BY tag.id DESC"
query, args, err := sqlx.Named(searchString, paramDict)
if err != nil {
@@ -224,5 +218,10 @@ func processMythicRPCTagSearch(msg amqp.Delivery) interface{} {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCTagSearch(incomingMessage)
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCTagSearch(incomingMessage, authContext)
}
@@ -5,9 +5,11 @@ import (
"encoding/json"
"errors"
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"github.com/its-a-feature/Mythic/database"
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
"github.com/its-a-feature/Mythic/utils/structs"
amqp "github.com/rabbitmq/amqp091-go"
)
@@ -32,25 +34,30 @@ func init() {
Queue: MYTHIC_RPC_TAGTYPE_GET_OR_CREATE, // swap out with queue in rabbitmq.constants.go file
RoutingKey: MYTHIC_RPC_TAGTYPE_GET_OR_CREATE, // swap out with routing key in rabbitmq.constants.go file
Handler: processMythicRPCTagtypeGetOrCreate, // points to function that takes in amqp.Delivery and returns interface{}
Scopes: []string{mythicjwt.SCOPE_TAG_WRITE},
})
}
// MYTHIC_RPC_OBJECT_ACTION - Say what the function does
func MythicRPCTagtypeGetOrCreate(input MythicRPCTagTypeGetOrCreateMessage) MythicRPCTagTypeGetOrCreateMessageResponse {
func MythicRPCTagtypeGetOrCreate(input MythicRPCTagTypeGetOrCreateMessage, authContext RabbitMQAuthContext) MythicRPCTagTypeGetOrCreateMessageResponse {
response := MythicRPCTagTypeGetOrCreateMessageResponse{
Success: false,
}
operationID := 0
err := database.DB.Get(&operationID, `SELECT task.operation_id FROM task WHERE id=$1`, input.TaskID)
if err != nil {
response.Error = err.Error()
return response
}
tt := databaseStructs.TagType{
Operation: operationID,
Operation: authContext.OperationID,
}
if authContext.APITokensID > 0 {
tt.APITokensID = structs.NullInt64{}
tt.APITokensID.Valid = true
tt.APITokensID.Int64 = int64(authContext.APITokensID)
}
if authContext.EventStepInstanceID > 0 {
tt.EventStepInstanceID = structs.NullInt64{}
tt.EventStepInstanceID.Valid = true
tt.EventStepInstanceID.Int64 = int64(authContext.EventStepInstanceID)
}
paramDict := make(map[string]interface{})
paramDict["operation_id"] = operationID
paramDict["operation_id"] = authContext.OperationID
searchString := `SELECT * FROM tagtype WHERE operation_id=:operation_id `
if input.GetOrCreateTagTypeID != nil {
searchString += `AND id=:id `
@@ -86,9 +93,9 @@ func MythicRPCTagtypeGetOrCreate(input MythicRPCTagTypeGetOrCreateMessage) Mythi
response.Error = "failed to find tagtype and no name provided to create one"
return response
}
statement, err := database.DB.PrepareNamed(`INSERT INTO tagtype
(name, description, color, operation_id)
VALUES (:name, :description, :color, :operation_id)
statement, err = database.DB.PrepareNamed(`INSERT INTO tagtype
(name, description, color, operation_id, apitokens_id, eventstepinstance_id)
VALUES (:name, :description, :color, :operation_id, :apitokens_id, :eventstepinstance_id)
RETURNING id`)
if err != nil {
response.Error = err.Error()
@@ -101,13 +108,13 @@ func MythicRPCTagtypeGetOrCreate(input MythicRPCTagTypeGetOrCreateMessage) Mythi
}
response.TagType = getTagTypeDataFromDatabaseTagType(tt)
return response
} else if err != nil {
}
if err != nil {
logging.LogError(err, "Failed to exec sqlx.IN modified statement")
response.Error = err.Error()
return response
} else {
response.TagType = getTagTypeDataFromDatabaseTagType(tt)
}
response.TagType = getTagTypeDataFromDatabaseTagType(tt)
response.Success = true
return response
}
@@ -132,5 +139,10 @@ func processMythicRPCTagtypeGetOrCreate(msg amqp.Delivery) interface{} {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCTagtypeGetOrCreate(incomingMessage)
authContext, err := GetRabbitMQAuthContextFromHeaders(msg.Headers)
if err != nil {
responseMsg.Error = err.Error()
return responseMsg
}
return MythicRPCTagtypeGetOrCreate(incomingMessage, authContext)
}

Some files were not shown because too many files have changed in this diff Show More