mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
finalizing dynamic parameters and ui updates for building
This commit is contained in:
@@ -120,7 +120,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5 # ref: https://github.com/marketplace/actions/build-and-push-docker-images
|
||||
with:
|
||||
context: postgres-docker
|
||||
file: postgres-docker/.docker/Dockerfile
|
||||
file: postgres-docker/Dockerfile
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_POSTGRES_IMAGE_NAME }}:${{ env.VERSION }}
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_POSTGRES_IMAGE_NAME }}:latest
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5 # ref: https://github.com/marketplace/actions/build-and-push-docker-images
|
||||
with:
|
||||
context: rabbitmq-docker
|
||||
file: rabbitmq-docker/.docker/Dockerfile
|
||||
file: rabbitmq-docker/Dockerfile
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_RABBITMQ_IMAGE_NAME }}:${{ env.VERSION }}
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_RABBITMQ_IMAGE_NAME }}:latest
|
||||
@@ -204,7 +204,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5 # ref: https://github.com/marketplace/actions/build-and-push-docker-images
|
||||
with:
|
||||
context: documentation-docker
|
||||
file: documentation-docker/.docker/Dockerfile
|
||||
file: documentation-docker/Dockerfile
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_DOCUMENTATION_IMAGE_NAME }}:${{ env.VERSION }}
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_DOCUMENTATION_IMAGE_NAME }}:latest
|
||||
@@ -248,7 +248,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5 # ref: https://github.com/marketplace/actions/build-and-push-docker-images
|
||||
with:
|
||||
context: jupyter-docker
|
||||
file: jupyter-docker/.docker/Dockerfile
|
||||
file: jupyter-docker/Dockerfile
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_JUPYTER_IMAGE_NAME }}:${{ env.VERSION }}
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_JUPYTER_IMAGE_NAME }}:latest
|
||||
@@ -292,7 +292,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5 # ref: https://github.com/marketplace/actions/build-and-push-docker-images
|
||||
with:
|
||||
context: hasura-docker
|
||||
file: hasura-docker/.docker/Dockerfile
|
||||
file: hasura-docker/Dockerfile
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_GRAPHQL_IMAGE_NAME }}:${{ env.VERSION }}
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_GRAPHQL_IMAGE_NAME }}:latest
|
||||
@@ -336,7 +336,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5 # ref: https://github.com/marketplace/actions/build-and-push-docker-images
|
||||
with:
|
||||
context: nginx-docker
|
||||
file: nginx-docker/.docker/Dockerfile
|
||||
file: nginx-docker/Dockerfile
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_NGINX_IMAGE_NAME }}:${{ env.VERSION }}
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_NGINX_IMAGE_NAME }}:latest
|
||||
@@ -382,7 +382,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5 # ref: https://github.com/marketplace/actions/build-and-push-docker-images
|
||||
with:
|
||||
context: mythic-react-docker
|
||||
file: mythic-react-docker/.docker/Dockerfile
|
||||
file: mythic-react-docker/Dockerfile
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_REACT_IMAGE_NAME }}:${{ env.VERSION }}
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_REACT_IMAGE_NAME }}:latest
|
||||
@@ -440,7 +440,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5 # ref: https://github.com/marketplace/actions/build-and-push-docker-images
|
||||
with:
|
||||
context: Mythic_CLI
|
||||
file: Mythic_CLI/.docker/Dockerfile
|
||||
file: Mythic_CLI/Dockerfile
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_CLI_IMAGE_NAME }}:${{ env.VERSION }}
|
||||
${{ env.REGISTRY }}/${{ env.MYTHIC_CLI_IMAGE_NAME }}:latest
|
||||
@@ -552,7 +552,7 @@ jobs:
|
||||
uses: EndBug/add-and-commit@v9 # ref: https://github.com/marketplace/actions/add-commit
|
||||
with:
|
||||
# Only add the Dockerfile changes. Nothing else should have been modified
|
||||
add: "['mythic-docker/Dockerfile', 'Mythic_CLI/Dockerfile', 'Mythic_CLI/Makefile', 'Mythic_CLI/src/cmd/config/constants.go', 'postgres-docker/Dockerfile', 'rabbitmq-docker/Dockerfile', 'documentation-docker/Dockerfile', 'jupyter-docker/Dockerfile', 'hasura-docker/Dockerfile', 'nginx-docker/Dockerfile', 'mythic-react-docker/Dockerfile']"
|
||||
add: "['Mythic_CLI/Makefile', 'Mythic_CLI/src/cmd/config/constants.go']"
|
||||
# Use the Github actions bot for the commit author
|
||||
default_author: github_actions
|
||||
committer_email: github-actions[bot]@users.noreply.github.com
|
||||
|
||||
+23
-2
@@ -4,13 +4,34 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [3.3.2.0] - 2025
|
||||
## [3.4.0] - 2025
|
||||
|
||||
### Changed
|
||||
|
||||
- Added "selected_os" array option to some eventing triggers to additionally limit not just on payload type but OS as well
|
||||
- this is the "os" you select when you build a payload
|
||||
-
|
||||
- Modified the UI to allow creating new eventing workflows based on existing ones with a single button
|
||||
- Added group_name, supported_os, and hide_conditions to build parameters for more dynamic build experiences
|
||||
- Added supports_multiple_c2_in_build, supports_multiple_c2_instances, c2_parameter_deviations to payload types
|
||||
- This allows payload types to slightly tweak c2 profile configurations they support and indicating how many c2s are allowed per build
|
||||
- c2 parameter deviations allow indicating parameters are not supported at all, that they have a different set of choices, and that the default value can be something else
|
||||
- Added semver option for payload types to more accurately convey their current version outside of their description
|
||||
- Updated the payload build workflow in the UI
|
||||
- Added an option for payload types to hook into the `help` function and provide their own as part of their payload type definition
|
||||
- Added a notification to callback tabs if there's new task changes while you're not on the tab
|
||||
- Updated delegate tasks for the same target callback to be grouped together into a single message instead of a separate message per task
|
||||
- Fixed a bug with MythicRPCCallbackSearch where all the timestamps would be 0
|
||||
- Added support for C2 Profiles to include their own agent icons for the UI
|
||||
- Added support for C2 Profiles to report their own SemVer as well
|
||||
- Added support for TranslationContainers to report back their own SemVer
|
||||
- Added support for all consuming containers (eventing, auth, logging, webhook) to report back their own SemVer
|
||||
- Fixed bug in reporting where tasks weren't in order
|
||||
- Updated the CredentialCreateWebhook to support empty credentials
|
||||
- Added callback context menu option for triggering eventing workflows
|
||||
- Also added option to bulk issue eventing workflows from callbacks
|
||||
- Edited workflows so that all can be triggered manually
|
||||
- Edited the server so manul execution can still provide arbitrary extra data, not just keyword execution
|
||||
- Updated operationeventlog to have a dedicated `warning` field that's separate from the level
|
||||
|
||||
## [3.3.1-rc94] - 2025-09-05
|
||||
|
||||
|
||||
Generated
+3
-3
@@ -6778,9 +6778,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001715",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz",
|
||||
"integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==",
|
||||
"version": "1.0.30001748",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz",
|
||||
"integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
||||
@@ -36,7 +36,7 @@ export const operatorSettingDefaults = {
|
||||
autoTaskLsOnEmptyDirectories: false,
|
||||
hideBrowserTasking: false,
|
||||
hideTaskingContext: false,
|
||||
taskingContextFields: ["impersonation_context", "cwd", "extra_info"],
|
||||
taskingContextFields: ["impersonation_context", "cwd"],
|
||||
["experiment-responseStreamLimit"]: 50,
|
||||
palette: {
|
||||
primary: {
|
||||
|
||||
+113
-111
@@ -39,6 +39,7 @@ import {InviteForm} from "./pages/Login/InviteForm";
|
||||
import {snackActions} from "./utilities/Snackbar";
|
||||
import {TopAppBarVertical} from "./TopAppBarVertical";
|
||||
|
||||
export const MeContext = createContext({});
|
||||
export const userSettingsQuery = gql`
|
||||
query getUserSettings {
|
||||
getOperatorPreferences {
|
||||
@@ -132,7 +133,7 @@ export function App(props) {
|
||||
topAppBarColor: themeMode === 'dark' ? preferences?.palette?.navBarColor?.dark || operatorSettingDefaults.palette.navBarColor.dark :
|
||||
preferences?.palette?.navBarColor?.light || operatorSettingDefaults.palette.navBarColor.light,
|
||||
typography: {
|
||||
fontSize: preferences?.fontSize,
|
||||
fontSize: 12, //preferences?.fontSize,
|
||||
fontFamily: preferences?.fontFamily
|
||||
},
|
||||
taskPromptTextColor: themeMode === 'dark' ? preferences?.palette?.taskPromptTextColor?.dark || operatorSettingDefaults.palette.taskPromptTextColor.dark :
|
||||
@@ -223,7 +224,7 @@ export function App(props) {
|
||||
topAppBarColor: themeMode === 'dark' ? operatorSettingDefaults.palette.navBarColor.dark :
|
||||
operatorSettingDefaults.palette.navBarColor.light,
|
||||
typography: {
|
||||
fontSize: operatorSettingDefaults?.fontSize,
|
||||
fontSize: 12,//operatorSettingDefaults?.fontSize,
|
||||
fontFamily: operatorSettingDefaults?.fontFamily
|
||||
},
|
||||
})
|
||||
@@ -295,122 +296,123 @@ export function App(props) {
|
||||
<ThemeProvider theme={theme}>
|
||||
<GlobalStyles theme={theme} />
|
||||
<CssBaseline />
|
||||
<Tooltip id={"my-tooltip"} style={{zIndex: 100000, wordBreak: "break-word", maxWidth: "80%", whiteSpace: "pre-wrap"}}/>
|
||||
<ToastContainer limit={2} autoClose={3000}
|
||||
theme={themeMode}
|
||||
hideProgressBar={true}
|
||||
newestOnTop={true}
|
||||
stacked={false}
|
||||
style={{maxWidth: "100%", minWidth: "40%", display: "flex", flexWrap: "wrap",
|
||||
wordBreak: "break-all", flexDirection: "column", justifyContent: "center",}}
|
||||
pauseOnFocusLoss={false} />
|
||||
<div style={{ maxHeight: '100%', height: '100%', display: 'flex', flexDirection: 'row', maxWidth: "100%", width:"100%",
|
||||
...background}}>
|
||||
<MeContext.Provider value={me}>
|
||||
<Tooltip id={"my-tooltip"} style={{zIndex: 100000, wordBreak: "break-word", maxWidth: "80%", whiteSpace: "pre-wrap"}}/>
|
||||
<ToastContainer limit={2} autoClose={3000}
|
||||
theme={themeMode}
|
||||
hideProgressBar={true}
|
||||
newestOnTop={true}
|
||||
stacked={false}
|
||||
style={{maxWidth: "100%", minWidth: "40%", display: "flex", flexWrap: "wrap",
|
||||
wordBreak: "break-all", flexDirection: "column", justifyContent: "center",}}
|
||||
pauseOnFocusLoss={false} />
|
||||
<div style={{ maxHeight: '100%', height: '100%', display: 'flex', flexDirection: 'row', maxWidth: "100%", width:"100%",
|
||||
...background}}>
|
||||
|
||||
{openRefreshDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="sm" open={openRefreshDialog}
|
||||
onClose={()=>{setOpenRefreshDialog(false);}}
|
||||
innerDialog={<RefreshTokenDialog
|
||||
onClose={()=>{setOpenRefreshDialog(false);}} />}
|
||||
/>
|
||||
}
|
||||
{me.loggedIn && me.user !== undefined && me.user !== null ? (
|
||||
<TopAppBarVertical me={me} theme={themeMode} toggleTheme={themeToggler} />
|
||||
) : null}
|
||||
<div style={{
|
||||
maxHeight: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: "100%"
|
||||
}}>
|
||||
{me.loggedIn && me?.user?.current_operation_banner_text !== "" &&
|
||||
<Typography style={{
|
||||
backgroundColor: me?.user?.current_operation_banner_color,
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
fontWeight: "600",
|
||||
color: "white",
|
||||
border: `1px solid ${theme.topAppBarColor || "grey"}`
|
||||
}}>
|
||||
{me?.user?.current_operation_banner_text}
|
||||
</Typography>
|
||||
}
|
||||
{me.loggedIn && me?.badConnection
|
||||
&&
|
||||
<Typography style={{
|
||||
backgroundColor: theme.palette.error.main,
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
fontWeight: "600",
|
||||
color: "white",
|
||||
border: `1px solid ${theme.topAppBarColor || "grey"}`
|
||||
}}>
|
||||
{"Can't connect to Mythic. Please check connection and refresh"}
|
||||
</Typography>
|
||||
{openRefreshDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="sm" open={openRefreshDialog}
|
||||
onClose={()=>{setOpenRefreshDialog(false);}}
|
||||
innerDialog={<RefreshTokenDialog
|
||||
onClose={()=>{setOpenRefreshDialog(false);}} />}
|
||||
/>
|
||||
}
|
||||
{me.loggedIn && me.user !== undefined && me.user !== null ? (
|
||||
<TopAppBarVertical me={me} theme={themeMode} toggleTheme={themeToggler} />
|
||||
) : null}
|
||||
<div style={{
|
||||
margin: '0px 0px 0px 0px',
|
||||
flexGrow: 1,
|
||||
overflowY: "hidden",
|
||||
maxHeight: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
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>}/>
|
||||
</Routes>
|
||||
{me.loggedIn && me?.user?.current_operation_banner_text !== "" &&
|
||||
<Typography style={{
|
||||
backgroundColor: me?.user?.current_operation_banner_color,
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
fontWeight: "600",
|
||||
color: "white",
|
||||
border: `1px solid ${theme.topAppBarColor || "grey"}`
|
||||
}}>
|
||||
{me?.user?.current_operation_banner_text}
|
||||
</Typography>
|
||||
}
|
||||
{me.loggedIn && me?.badConnection
|
||||
&&
|
||||
<Typography style={{
|
||||
backgroundColor: theme.palette.error.main,
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
fontWeight: "600",
|
||||
color: "white",
|
||||
border: `1px solid ${theme.topAppBarColor || "grey"}`
|
||||
}}>
|
||||
{"Can't connect to Mythic. Please check connection and refresh"}
|
||||
</Typography>
|
||||
}
|
||||
<div style={{
|
||||
margin: '0px 0px 0px 0px',
|
||||
flexGrow: 1,
|
||||
overflowY: "hidden",
|
||||
flexDirection: 'column',
|
||||
height: "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>}/>
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</MeContext.Provider>
|
||||
</ThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ const subscribe_payloads = gql`
|
||||
level
|
||||
resolved
|
||||
source
|
||||
warning
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -33,7 +34,10 @@ export function EventFeedNotifications(props) {
|
||||
},
|
||||
onData: ({data}) => {
|
||||
if (data.data.operationeventlog_stream.length > 0){
|
||||
if(data.data.operationeventlog_stream[0].level === "api" || data.data.operationeventlog_stream[0].level === "debug" ){
|
||||
if(data.data.operationeventlog_stream[0].level === "api" ||
|
||||
data.data.operationeventlog_stream[0].level === "debug" ||
|
||||
data.data.operationeventlog_stream[0].level === "agent_message"
|
||||
){
|
||||
return;
|
||||
}
|
||||
if(data.data.operationeventlog_stream[0].resolved){
|
||||
@@ -41,14 +45,14 @@ export function EventFeedNotifications(props) {
|
||||
}
|
||||
if(data.data.operationeventlog_stream[0].operator){
|
||||
const message = data.data.operationeventlog_stream[0].operator.username + ":\n" + data.data.operationeventlog_stream[0].message;
|
||||
if (data.data.operationeventlog_stream[0].level === "warning") {
|
||||
if (data.data.operationeventlog_stream[0].warning) {
|
||||
snackActions.warning(message, { autoClose: 2000});
|
||||
} else {
|
||||
snackActions.info(message, { autoClose: 2000});
|
||||
}
|
||||
|
||||
}else {
|
||||
if (data.data.operationeventlog_stream[0].level === "warning") {
|
||||
if (data.data.operationeventlog_stream[0].warning) {
|
||||
snackActions.warning(data.data.operationeventlog_stream[0].message, {autoClose: 3000});
|
||||
} else {
|
||||
snackActions.info(
|
||||
|
||||
@@ -1,16 +1,52 @@
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import {MythicStyledTooltip} from "./MythicStyledTooltip";
|
||||
import WifiIcon from '@mui/icons-material/Wifi';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import { faLink } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export const MythicAgentSVGIcon = ({payload_type, style}) => {
|
||||
export const MythicAgentSVGIcon = ({payload_type, style, is_p2p}) => {
|
||||
const theme = useTheme();
|
||||
if(payload_type === ""){
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<MythicStyledTooltip title={payload_type}>
|
||||
<MythicStyledTooltip title={payload_type} >
|
||||
<img src={"/static/" + payload_type + "_" + theme.palette.mode + ".svg"}
|
||||
style={{...style}}/>
|
||||
style={{...style}} />
|
||||
{is_p2p === false &&
|
||||
<WifiIcon style={{width: "20px", height: "20px", marginLeft: "-20px", marginBottom: "5px",
|
||||
borderRadius: "10px", border: "1px solid",
|
||||
backgroundColor: theme.palette.primary.main}}/>
|
||||
}
|
||||
{is_p2p === true &&
|
||||
<FontAwesomeIcon icon={faLink} style={{width: "20px", height: "20px", marginLeft: "-20px", marginBottom: "5px",
|
||||
borderRadius: "10px", border: "1px solid",
|
||||
backgroundColor: theme.palette.primary.main}} />
|
||||
}
|
||||
</MythicStyledTooltip>
|
||||
|
||||
)
|
||||
}
|
||||
export const MythicAgentSVGIconNoTooltip = ({payload_type, is_p2p, className}) => {
|
||||
const theme = useTheme();
|
||||
if(payload_type === ""){
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<div style={{display: "inline-block", left: "50%"}} className={className}>
|
||||
<img src={"/static/" + payload_type + "_" + theme.palette.mode + ".svg"} style={{height: "20px", margin: 'auto'}}/>
|
||||
{is_p2p === false &&
|
||||
<WifiIcon style={{width: "10px", height: "10px", marginLeft: "-5px", marginBottom: "0px",
|
||||
borderRadius: "10px", border: "1px solid",
|
||||
backgroundColor: theme.palette.primary.main, color: "white" }} />
|
||||
}
|
||||
{is_p2p === true &&
|
||||
<FontAwesomeIcon icon={faLink} style={{width: "20px", height: "20px", marginLeft: "-20px", marginBottom: "5px",
|
||||
borderRadius: "10px", border: "1px solid",
|
||||
backgroundColor: theme.palette.primary.main, color: "white"}} />
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import MenuItem from '@mui/material/MenuItem';
|
||||
import MenuList from '@mui/material/MenuList';
|
||||
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Badge from '@mui/material/Badge';
|
||||
import {
|
||||
Draggable,
|
||||
} from "@hello-pangea/dnd";
|
||||
@@ -60,6 +61,8 @@ export function MythicTabLabel(props) {
|
||||
getCallbackData,
|
||||
onDragTab,
|
||||
selectedIndex,
|
||||
highlight,
|
||||
newDataForTab,
|
||||
...other
|
||||
} = props;
|
||||
const onClick = (e) => {
|
||||
@@ -87,7 +90,7 @@ export function MythicTabLabel(props) {
|
||||
setOpenContextMenu(false);
|
||||
};
|
||||
return (
|
||||
<Draggable draggableId={`callbacks-tab-${label}`} index={index} disableInteractiveElementBlocking={true}>
|
||||
<Draggable draggableId={`callbacks-tab-${tabInfo.tabID}`} index={index} disableInteractiveElementBlocking={true}>
|
||||
{(provided, snapshot) => (
|
||||
<div ref={provided.innerRef}
|
||||
{...provided.draggableProps}>
|
||||
@@ -95,7 +98,9 @@ export function MythicTabLabel(props) {
|
||||
className={selectedIndex === index || snapshot.isDragging ? "selectedCallback" : "" }
|
||||
label={
|
||||
<span onContextMenu={handleContextClick} style={{}} ref={dropdownAnchorRef}>
|
||||
{label}
|
||||
<Badge color="success" variant="dot" invisible={!highlight} >
|
||||
{label}
|
||||
</Badge>
|
||||
<IconButton component='div' size='small' onClick={onClick} {...other}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
|
||||
@@ -11,7 +11,7 @@ import FormControl from '@mui/material/FormControl';
|
||||
import Select from '@mui/material/Select';
|
||||
import { Typography } from '@mui/material';
|
||||
import { ReactFlow,
|
||||
applyEdgeChanges,
|
||||
EdgeLabelRenderer,getBezierPath, BaseEdge,
|
||||
Handle, Position, useReactFlow, ReactFlowProvider, Panel,
|
||||
MiniMap, Controls, ControlButton, useUpdateNodeInternals,
|
||||
getConnectedEdges, useNodesState, useEdgesState
|
||||
@@ -37,6 +37,7 @@ import SendIcon from '@mui/icons-material/Send';
|
||||
import {getIconName} from "./ResponseDisplayTable";
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
||||
import { MythicAgentSVGIconNoTooltip} from "../../MythicComponents/MythicAgentSVGIcon";
|
||||
|
||||
const ITEM_HEIGHT = 48;
|
||||
const ITEM_PADDING_TOP = 8;
|
||||
@@ -459,6 +460,41 @@ export function GroupNode({data}) {
|
||||
}
|
||||
const nodeTypes = { "agentNode": AgentNode, "groupNode": GroupNode, "taskNode": TaskNode, "browserscriptNode": BrowserscriptNode };
|
||||
|
||||
export function C2LabelEdge({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data,
|
||||
labelStyle, markerStart, markerEnd, labelShowBg, labelBgStyle, style, label,
|
||||
interactionWidth
|
||||
}){
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<BaseEdge id={id} path={edgePath} labelStyle={labelStyle} markerEnd={markerEnd} markerStart={markerStart}
|
||||
labelShowBg={labelShowBg} labelBgStyle={labelBgStyle} style={style} label={label}
|
||||
interactionWidth={interactionWidth} labelX={labelX} labelY={labelY}/>
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
style={{
|
||||
transform: `translate(-1%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
}}
|
||||
className="nodrag nopan"
|
||||
>
|
||||
{data.has_logo && <MythicAgentSVGIconNoTooltip payload_type={label}
|
||||
is_p2p={data.is_p2p}
|
||||
className={"circleImageNode"}/>}
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
const edgeTypes = {
|
||||
C2IconEdge: C2LabelEdge
|
||||
};
|
||||
const elk = new ELK();
|
||||
const getWidth = (node) => {
|
||||
if(node.type === "taskNode"){
|
||||
@@ -854,9 +890,12 @@ export const DrawC2PathElementsFlow = ({edges, panel, view_config, contextMenu,
|
||||
mythic_id: edge.id,
|
||||
source: `${edge.source.id}`,
|
||||
target: "Mythic",
|
||||
label: edge.c2profile.name,
|
||||
type: "C2IconEdge",
|
||||
animated: true,
|
||||
label: edge.c2profile.name,
|
||||
data: {
|
||||
has_logo: edge.c2profile.has_logo,
|
||||
is_p2p: edge.c2profile.is_p2p,
|
||||
source: {...edge.source, parentId: getGroupBy(edge.source, view_config)},
|
||||
target: {parentId: "Mythic"},
|
||||
end_timestamp: edge.end_timestamp,
|
||||
@@ -970,8 +1009,11 @@ export const DrawC2PathElementsFlow = ({edges, panel, view_config, contextMenu,
|
||||
target: `${edge.source.id}`,
|
||||
label: edge.c2profile.name,
|
||||
animated: true,
|
||||
type: "C2IconEdge",
|
||||
data: {
|
||||
end_timestamp: edge.end_timestamp,
|
||||
has_logo: edge.c2profile.has_logo,
|
||||
is_p2p: edge.c2profile.is_p2p,
|
||||
source: {...edge.destination, parentId: getGroupBy(edge.destination, view_config)},
|
||||
target: {...edge.source, parentId: getGroupBy(edge.source, view_config)},
|
||||
}
|
||||
@@ -1004,8 +1046,11 @@ export const DrawC2PathElementsFlow = ({edges, panel, view_config, contextMenu,
|
||||
target: `${edge.destination.id}`,
|
||||
label: edge.c2profile.name,
|
||||
animated: true,
|
||||
type: "C2IconEdge",
|
||||
data: {
|
||||
end_timestamp: edge.end_timestamp,
|
||||
has_logo: edge.c2profile.has_logo,
|
||||
is_p2p: edge.c2profile.is_p2p,
|
||||
source: {...edge.source, parentId: getGroupBy(edge.source, view_config)},
|
||||
target: {...edge.target, parentId: getGroupBy(edge.target, view_config)},
|
||||
}
|
||||
@@ -1040,8 +1085,11 @@ export const DrawC2PathElementsFlow = ({edges, panel, view_config, contextMenu,
|
||||
target: `${edge.destination.id}`,
|
||||
label: edge.c2profile.name,
|
||||
animated: true,
|
||||
type: "C2IconEdge",
|
||||
data: {
|
||||
end_timestamp: edge.end_timestamp,
|
||||
has_logo: edge.c2profile.has_logo,
|
||||
is_p2p: edge.c2profile.is_p2p,
|
||||
source: {...edge.source, parentId: getGroupBy(edge.source, view_config)},
|
||||
target: {...edge.target, parentId: getGroupBy(edge.destination, view_config)},
|
||||
}
|
||||
@@ -1239,7 +1287,7 @@ export const DrawC2PathElementsFlow = ({edges, panel, view_config, contextMenu,
|
||||
tempEdges[i].markerEnd.type = "arrowclosed"
|
||||
tempEdges[i].labelBgStyle = {
|
||||
fill: theme.tableHover,
|
||||
fillOpacity: 0.6,
|
||||
fillOpacity: 0.8,
|
||||
}
|
||||
tempEdges[i].labelStyle = {
|
||||
fill: theme.palette.background.contrast,
|
||||
@@ -1270,6 +1318,7 @@ export const DrawC2PathElementsFlow = ({edges, panel, view_config, contextMenu,
|
||||
target: tempEdges[j].data.target.parentId,
|
||||
label: "",
|
||||
hidden: true,
|
||||
type: "C2IconEdge",
|
||||
data: {
|
||||
source: {parentId: parentIds[i].id},
|
||||
target: tempEdges[j].data.target,
|
||||
@@ -1289,6 +1338,7 @@ export const DrawC2PathElementsFlow = ({edges, panel, view_config, contextMenu,
|
||||
target: tempEdges[j].data.target.parentId,
|
||||
label: "",
|
||||
hidden: true,
|
||||
type: "C2IconEdge",
|
||||
data: {
|
||||
source: {parentId: parentIds[i].id},
|
||||
target: tempEdges[j].data.target,
|
||||
@@ -1368,6 +1418,7 @@ export const DrawC2PathElementsFlow = ({edges, panel, view_config, contextMenu,
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onPaneClick={onPaneClick}
|
||||
onNodeContextMenu={onNodeContextMenu}
|
||||
onNodeClick={onNodeSelected}
|
||||
|
||||
@@ -30,6 +30,7 @@ export const getCallbackIdFromClickedTab = (tabId) => {
|
||||
export function Callbacks({me}) {
|
||||
const [openCallbackImport, setOpenCallbackImport] = React.useState(false);
|
||||
const [topDisplay, setTopDisplay] = React.useState('table');
|
||||
const [newDataForTab, setNewDataForTab] = React.useState({});
|
||||
const [openTabs, setOpenTabs] = React.useState([]);
|
||||
const [clickedTabId, setClickedTabIdValue] = React.useState('');
|
||||
const openTabRef = React.useRef([]);
|
||||
@@ -46,6 +47,7 @@ export function Callbacks({me}) {
|
||||
}
|
||||
}
|
||||
setClickedTabIdValue(tabID);
|
||||
setNewDataForTab((prev) => {return {...prev, [tabID]: false}});
|
||||
}
|
||||
useEffect(() => {
|
||||
const oldTabs = localStorage.getItem('openTabs');
|
||||
@@ -257,6 +259,8 @@ export function Callbacks({me}) {
|
||||
me={me}
|
||||
onDragEnd={onDragEnd}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
newDataForTab={newDataForTab}
|
||||
setNewDataForTab={setNewDataForTab}
|
||||
/>
|
||||
</div>
|
||||
</Split>
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
} from './CallbacksTableRow';
|
||||
import MythicResizableGrid from '../../MythicComponents/MythicResizableGrid';
|
||||
import {TableFilterDialog} from './TableFilterDialog';
|
||||
import {CallbacksTabsHideMultipleDialog} from "./CallbacksTabsHideMultipleDialog";
|
||||
import {CallbacksTabsHideMultipleDialog, CallbacksTabsSelectMultipleDialog} from "./CallbacksTabsHideMultipleDialog";
|
||||
import {CallbacksTabsTaskMultipleDialog} from "./CallbacksTabsTaskMultipleDialog";
|
||||
import ip6 from 'ip6';
|
||||
import {CallbacksContext, OnOpenTabContext, OnOpenTabsContext} from "./CallbacksTop";
|
||||
@@ -53,6 +53,9 @@ import NotificationsActiveTwoToneIcon from '@mui/icons-material/NotificationsAct
|
||||
import NotificationsOffTwoToneIcon from '@mui/icons-material/NotificationsOffTwoTone';
|
||||
import {CallbacksTableEditTriggerOnCheckinDialog} from "./CallbacksTableEditTriggerOnCheckinDialog";
|
||||
import {CallbacksTableColumnsReorderDialog} from "./CallbacksTableColumnsReorderDialog";
|
||||
import PlayCircleFilledTwoToneIcon from '@mui/icons-material/PlayCircleFilledTwoTone';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import {EventTriggerContextSelectDialog} from "../Eventing/EventTriggerContextSelect";
|
||||
|
||||
export const ipCompare = (a, b) => {
|
||||
let aJSON = JSON.parse(a);
|
||||
@@ -316,6 +319,9 @@ function CallbacksTablePreMemo(props){
|
||||
})
|
||||
const taskingData = React.useRef({"parameters": "", "ui_feature": "callback_table:exit"});
|
||||
const [openTaskingButton, setOpenTaskingButton] = React.useState(false);
|
||||
const eventingDataRef = React.useRef({});
|
||||
const [openEventingDialog, setOpenEventingDialog] = React.useState(false);
|
||||
const [openEventingMultipleDialog, setOpenEventingMultipleDialog] = React.useState(false);
|
||||
const [updateSetting] = useSetMythicSetting();
|
||||
const onUpdateTrigger = (newTriggerValue) => {
|
||||
updateTrigger({variables: {callback_display_id: openTriggerDialog.display_id, trigger_on_checkin_after_time: newTriggerValue}})
|
||||
@@ -331,7 +337,14 @@ function CallbacksTablePreMemo(props){
|
||||
}
|
||||
return [
|
||||
{
|
||||
name: "Callback: " + rowDataStatic.display_id,
|
||||
name: <div style={{display: "flex", flexDirection: "column"}}>
|
||||
<Typography>
|
||||
{"Callback: " + rowDataStatic.display_id}
|
||||
</Typography>
|
||||
<Typography style={{fontSize: theme?.typography.pxToRem(12),}}>
|
||||
{rowDataStatic.user + "@" + rowDataStatic.host}
|
||||
</Typography>
|
||||
</div>,
|
||||
icon: null, click: ({event}) => {},
|
||||
type: "item",
|
||||
disabled: true
|
||||
@@ -399,6 +412,21 @@ function CallbacksTablePreMemo(props){
|
||||
setOpenTriggerDialog({open: true, trigger_on_checkin_after_time: rowDataStatic.trigger_on_checkin_after_time, display_id: rowDataStatic.display_id})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Eventing", icon: <PlayCircleFilledTwoToneIcon />, click: (event) => {}, type: "menu",
|
||||
menuItems: [
|
||||
{
|
||||
name: "Start Eventing Workflow", icon: <PlayCircleFilledTwoToneIcon style={{paddingRight: "5px"}} />,
|
||||
click: ({event}) => {
|
||||
eventingDataRef.current = {
|
||||
name: "callback_id",
|
||||
value: rowDataStatic.id
|
||||
};
|
||||
setOpenEventingDialog(true);
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Browsers", icon: null, click: () => {}, type: "menu",
|
||||
menuItems: [
|
||||
@@ -445,6 +473,12 @@ function CallbacksTablePreMemo(props){
|
||||
setOpenTaskMultipleDialog({open: true, data: rowDataStatic});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Start Multiple Eventing Workflows", icon: <PlayCircleFilledTwoToneIcon style={{paddingRight: "5px"}}/>,
|
||||
click: ({event}) => {
|
||||
setOpenEventingMultipleDialog(true);
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -904,6 +938,13 @@ function CallbacksTablePreMemo(props){
|
||||
const onResetColumnReorder = () => {
|
||||
onSubmitColumnReorder(callbackTableInitialColumns);
|
||||
}
|
||||
const onSubmitSelectMultiple = (callbacks) => {
|
||||
eventingDataRef.current = {
|
||||
trigger_context_type: "callback_id",
|
||||
trigger_context_ids: callbacks.map(c => c.id)
|
||||
};
|
||||
setOpenEventingDialog(true);
|
||||
}
|
||||
return (
|
||||
<div style={{width: '100%', height: '100%', position: "relative",}}>
|
||||
<MythicResizableGrid
|
||||
@@ -1081,6 +1122,31 @@ function CallbacksTablePreMemo(props){
|
||||
}
|
||||
/>
|
||||
}
|
||||
{openEventingDialog &&
|
||||
<MythicDialog
|
||||
fullWidth={true}
|
||||
maxWidth="xl"
|
||||
open={openEventingDialog}
|
||||
onClose={() => {setOpenEventingDialog(false);}}
|
||||
innerDialog={
|
||||
<EventTriggerContextSelectDialog onClose={() => {setOpenEventingDialog(false);}}
|
||||
triggerContext={eventingDataRef.current}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
{openEventingMultipleDialog &&
|
||||
<MythicDialog
|
||||
fullWidth={true}
|
||||
maxWidth="xl"
|
||||
open={openEventingMultipleDialog}
|
||||
onClose={() => {setOpenEventingMultipleDialog(false);}}
|
||||
innerDialog={
|
||||
<CallbacksTabsSelectMultipleDialog onClose={() => {setOpenEventingMultipleDialog(false);}}
|
||||
onSubmit={onSubmitSelectMultiple}/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { CallbacksTabsProcessBrowserLabel, CallbacksTabsProcessBrowserPanel } fr
|
||||
import { CallbacksTabsTaskingSplitLabel, CallbacksTabsTaskingSplitPanel} from "./CallbacksTabsTaskingSplit";
|
||||
import {CallbacksTabsTaskingConsoleLabel, CallbacksTabsTaskingConsolePanel} from "./CallbacksTabsTaskingConsole";
|
||||
|
||||
export function CallbacksTabs({ onCloseTab, openTabs, onDragTab, onDragEnd, clickedTabId, setClickedTabId, onEditTabDescription, contextMenuOptions, me}) {
|
||||
export function CallbacksTabs({ onCloseTab, openTabs, onDragTab, onDragEnd, clickedTabId, setClickedTabId, onEditTabDescription, contextMenuOptions, me, newDataForTab, setNewDataForTab}) {
|
||||
|
||||
const mountedRef = React.useRef(true);
|
||||
const [value, setValue] = React.useState(0);
|
||||
@@ -75,94 +75,103 @@ export function CallbacksTabs({ onCloseTab, openTabs, onDragTab, onDragEnd, clic
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="callback-tabs-list" direction={"horizontal"}>
|
||||
{(provided) => (
|
||||
<Tabs
|
||||
ref={provided.innerRef} {...provided.droppableProps}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
indicatorColor='primary'
|
||||
textColor='primary'
|
||||
variant='fullWidth'
|
||||
scrollButtons={true}
|
||||
style={{ }}
|
||||
TabIndicatorProps={{style: {display: "none"}}}
|
||||
aria-label='scrollable tabs'>
|
||||
{openTabs.map((tab, index) => {
|
||||
switch (tab.tabType) {
|
||||
case 'interact':
|
||||
return (
|
||||
<CallbacksTabsTaskingLabel
|
||||
onEditTabDescription={onEditTabDescription}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tablabel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
index={index}
|
||||
me={me}
|
||||
selectedIndex={value}
|
||||
onDragTab={onDragTab}
|
||||
contextMenuOptions={modifiedInteractContextMenuOptions}
|
||||
/>
|
||||
);
|
||||
case 'interactSplit':
|
||||
return (
|
||||
<CallbacksTabsTaskingSplitLabel
|
||||
onEditTabDescription={onEditTabDescription}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tablabel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
index={index}
|
||||
me={me}
|
||||
selectedIndex={value}
|
||||
onDragTab={onDragTab}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
/>
|
||||
)
|
||||
case 'interactConsole':
|
||||
return (
|
||||
<CallbacksTabsTaskingConsoleLabel
|
||||
onEditTabDescription={onEditTabDescription}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tablabel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
index={index}
|
||||
me={me}
|
||||
selectedIndex={value}
|
||||
onDragTab={onDragTab}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
/>
|
||||
)
|
||||
case 'fileBrowser':
|
||||
return (
|
||||
<CallbacksTabsFileBrowserLabel
|
||||
onEditTabDescription={onEditTabDescription}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tablabel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
index={index}
|
||||
me={me}
|
||||
selectedIndex={value}
|
||||
onDragTab={onDragTab}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
/>
|
||||
);
|
||||
case 'processBrowser':
|
||||
return (
|
||||
<CallbacksTabsProcessBrowserLabel
|
||||
onEditTabDescription={onEditTabDescription}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tablabel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
index={index}
|
||||
me={me}
|
||||
selectedIndex={value}
|
||||
onDragTab={onDragTab}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Tabs>
|
||||
<>
|
||||
<Tabs
|
||||
ref={provided.innerRef} {...provided.droppableProps}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
indicatorColor='primary'
|
||||
textColor='primary'
|
||||
variant='fullWidth'
|
||||
scrollButtons={true}
|
||||
style={{ }}
|
||||
TabIndicatorProps={{style: {display: "none"}}}
|
||||
aria-label='scrollable tabs'>
|
||||
{openTabs.map((tab, index) => {
|
||||
switch (tab.tabType) {
|
||||
case 'interact':
|
||||
return (
|
||||
<CallbacksTabsTaskingLabel
|
||||
onEditTabDescription={onEditTabDescription}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tablabel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
index={index}
|
||||
me={me}
|
||||
newDataForTab={newDataForTab}
|
||||
selectedIndex={value}
|
||||
onDragTab={onDragTab}
|
||||
contextMenuOptions={modifiedInteractContextMenuOptions}
|
||||
/>
|
||||
);
|
||||
case 'interactSplit':
|
||||
return (
|
||||
<CallbacksTabsTaskingSplitLabel
|
||||
onEditTabDescription={onEditTabDescription}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tablabel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
index={index}
|
||||
me={me}
|
||||
newDataForTab={newDataForTab}
|
||||
selectedIndex={value}
|
||||
onDragTab={onDragTab}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
/>
|
||||
)
|
||||
case 'interactConsole':
|
||||
return (
|
||||
<CallbacksTabsTaskingConsoleLabel
|
||||
onEditTabDescription={onEditTabDescription}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tablabel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
index={index}
|
||||
me={me}
|
||||
newDataForTab={newDataForTab}
|
||||
selectedIndex={value}
|
||||
onDragTab={onDragTab}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
/>
|
||||
)
|
||||
case 'fileBrowser':
|
||||
return (
|
||||
<CallbacksTabsFileBrowserLabel
|
||||
onEditTabDescription={onEditTabDescription}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tablabel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
index={index}
|
||||
me={me}
|
||||
newDataForTab={newDataForTab}
|
||||
selectedIndex={value}
|
||||
onDragTab={onDragTab}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
/>
|
||||
);
|
||||
case 'processBrowser':
|
||||
return (
|
||||
<CallbacksTabsProcessBrowserLabel
|
||||
onEditTabDescription={onEditTabDescription}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tablabel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
index={index}
|
||||
me={me}
|
||||
newDataForTab={newDataForTab}
|
||||
selectedIndex={value}
|
||||
onDragTab={onDragTab}
|
||||
contextMenuOptions={contextMenuOptions}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
{provided.placeholder}
|
||||
</Tabs>
|
||||
</>
|
||||
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
@@ -182,6 +191,7 @@ export function CallbacksTabs({ onCloseTab, openTabs, onDragTab, onDragEnd, clic
|
||||
key={'tabpanel' + tab.tabID + tab.tabType}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
tabInfo={tab}
|
||||
setNewDataForTab={setNewDataForTab}
|
||||
value={value}
|
||||
index={index}
|
||||
me={me}
|
||||
@@ -201,6 +211,7 @@ export function CallbacksTabs({ onCloseTab, openTabs, onDragTab, onDragEnd, clic
|
||||
key={'tabpanel' + tab.tabID + tab.tabType}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
tabInfo={tab}
|
||||
setNewDataForTab={setNewDataForTab}
|
||||
value={value}
|
||||
index={index}
|
||||
me={me}
|
||||
@@ -219,6 +230,7 @@ export function CallbacksTabs({ onCloseTab, openTabs, onDragTab, onDragEnd, clic
|
||||
key={'tabpanel' + tab.tabID + tab.tabType}
|
||||
onCloseTab={onCloseTabLocal}
|
||||
tabInfo={tab}
|
||||
setNewDataForTab={setNewDataForTab}
|
||||
value={value}
|
||||
index={index}
|
||||
me={me}
|
||||
@@ -237,6 +249,7 @@ export function CallbacksTabs({ onCloseTab, openTabs, onDragTab, onDragEnd, clic
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tabpanel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
setNewDataForTab={setNewDataForTab}
|
||||
value={value}
|
||||
index={index}
|
||||
me={me}
|
||||
@@ -255,6 +268,7 @@ export function CallbacksTabs({ onCloseTab, openTabs, onDragTab, onDragEnd, clic
|
||||
onCloseTab={onCloseTabLocal}
|
||||
key={'tabpanel' + tab.tabID + tab.tabType}
|
||||
tabInfo={tab}
|
||||
setNewDataForTab={setNewDataForTab}
|
||||
value={value}
|
||||
index={index}
|
||||
me={me}
|
||||
|
||||
@@ -173,7 +173,7 @@ export function CallbacksTabsFileBrowserLabel(props) {
|
||||
]);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MythicTabLabel label={description} onDragTab={props.onDragTab} {...props} contextMenuOptions={contextMenuOptions} />
|
||||
<MythicTabLabel label={description} highlight={props.newDataForTab[props.tabInfo.tabID]} onDragTab={props.onDragTab} {...props} contextMenuOptions={contextMenuOptions} />
|
||||
{openEditDescriptionDialog && (
|
||||
<MythicDialog
|
||||
fullWidth={true}
|
||||
@@ -196,7 +196,7 @@ export function CallbacksTabsFileBrowserLabel(props) {
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me }) => {
|
||||
export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me, setNewDataForTab }) => {
|
||||
const fromNow = React.useRef(getSkewedNow());
|
||||
const [backdropOpen, setBackdropOpen] = React.useState(false);
|
||||
const treeRootDataRef = React.useRef({}); // hold all of the actual data
|
||||
@@ -360,7 +360,9 @@ export const CallbacksTabsFileBrowserPanel = ({ index, value, tabInfo, me }) =>
|
||||
return prev;
|
||||
}, {...treeAdjMtx});
|
||||
setTreeAdjMtx(newMatrix);
|
||||
|
||||
if(index !== value){
|
||||
setNewDataForTab((prev) => {return {...prev, [tabInfo.tabID]: true}});
|
||||
}
|
||||
}
|
||||
})
|
||||
useSubscription(fileBrowserTaskSub, {
|
||||
|
||||
@@ -92,7 +92,7 @@ const columns = [
|
||||
flex: 0.5,
|
||||
}
|
||||
];
|
||||
const CustomSelectTable = ({initialData, selectedData}) => {
|
||||
const CustomSelectTable = ({initialData, selectedData, sortModel}) => {
|
||||
const [data, setData] = React.useState([]);
|
||||
const [rowSelectionModel, setRowSelectionModel] = React.useState({
|
||||
type: 'include',
|
||||
@@ -120,7 +120,7 @@ const CustomSelectTable = ({initialData, selectedData}) => {
|
||||
},
|
||||
},
|
||||
sorting: {
|
||||
sortModel: [{ field: 'last_checkin', sort: 'asc' }],
|
||||
sortModel: [sortModel],
|
||||
},
|
||||
}}
|
||||
autoPageSize
|
||||
@@ -176,7 +176,9 @@ export function CallbacksTabsHideMultipleDialog({onClose}) {
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Hide Multiple Callbacks at Once</DialogTitle>
|
||||
<CustomSelectTable initialData={initialData}
|
||||
selectedData={selectedData} />
|
||||
selectedData={selectedData}
|
||||
sortModel={{ field: 'last_checkin', sort: 'asc' }}
|
||||
/>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="contained" color="primary">
|
||||
Close
|
||||
@@ -188,4 +190,46 @@ export function CallbacksTabsHideMultipleDialog({onClose}) {
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
export function CallbacksTabsSelectMultipleDialog({onClose, onSubmit}) {
|
||||
|
||||
const selectedData = React.useRef([]);
|
||||
const [initialData, setInitialData] = React.useState([]);
|
||||
useQuery(callbacksAndFeaturesQuery,{
|
||||
fetchPolicy: "no-cache",
|
||||
onCompleted: (data) => {
|
||||
const callbackData = data.callback.map( c => {
|
||||
// for each callback, get a unique set of supported features
|
||||
const display = `${c.display_id} - ${c.user}@${c.host} (${c.pid}) - ${c.description}`;
|
||||
return {...c, display};
|
||||
});
|
||||
setInitialData(callbackData);
|
||||
}
|
||||
});
|
||||
const submitTasking = () => {
|
||||
if(selectedData.current.length === 0){
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
onSubmit(selectedData.current);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Select Multiple Callbacks</DialogTitle>
|
||||
<CustomSelectTable initialData={initialData}
|
||||
selectedData={selectedData}
|
||||
sortModel={{ field: 'display_id', sort: 'desc' }}
|
||||
/>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="contained" color="primary">
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={submitTasking} variant="contained" color="warning">
|
||||
Use Selection
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ export function CallbacksTabsProcessBrowserLabel(props){
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MythicTabLabel label={description} onDragTab={props.onDragTab} {...props} contextMenuOptions={contextMenuOptions}/>
|
||||
<MythicTabLabel label={description} highlight={props.newDataForTab[props.tabInfo.tabID]} onDragTab={props.onDragTab} {...props} contextMenuOptions={contextMenuOptions}/>
|
||||
{openEditDescriptionDialog &&
|
||||
<MythicDialog fullWidth={true} open={openEditDescriptionDialog} onClose={() => {setOpenEditDescriptionDialog(false);}}
|
||||
innerDialog={
|
||||
@@ -143,7 +143,7 @@ export function CallbacksTabsProcessBrowserLabel(props){
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
export const CallbacksTabsProcessBrowserPanel = ({index, value, tabInfo, me}) =>{
|
||||
export const CallbacksTabsProcessBrowserPanel = ({index, value, tabInfo, me, setNewDataForTab}) =>{
|
||||
const fromNow = React.useRef((new Date()));
|
||||
const [backdropOpen, setBackdropOpen] = React.useState(false);
|
||||
const [expandOrCollapseAll, setExpandOrCollapseAll] = React.useState(false);
|
||||
@@ -290,6 +290,9 @@ export const CallbacksTabsProcessBrowserPanel = ({index, value, tabInfo, me}) =>
|
||||
}
|
||||
const newMatrix = getNewMatrix();
|
||||
setTreeAdjMtx(newMatrix);
|
||||
if(index !== value){
|
||||
setNewDataForTab((prev) => {return {...prev, [tabInfo.tabID]: true}});
|
||||
}
|
||||
}
|
||||
});
|
||||
const getHostProcessesQuerySuccess = (data) => {
|
||||
|
||||
@@ -48,7 +48,7 @@ export function CallbacksTabsTaskingLabel(props){
|
||||
]);
|
||||
return (
|
||||
<>
|
||||
<MythicTabLabel label={description} onDragTab={props.onDragTab} me={props.me} {...props} contextMenuOptions={contextMenuOptions}/>
|
||||
<MythicTabLabel highlight={props.newDataForTab[props.tabInfo.tabID]} label={description} onDragTab={props.onDragTab} me={props.me} {...props} contextMenuOptions={contextMenuOptions}/>
|
||||
{openEditDescriptionDialog &&
|
||||
<MythicDialog fullWidth={true} open={openEditDescriptionDialog} onClose={() => {setOpenEditDescriptionDialog(false);}}
|
||||
innerDialog={
|
||||
@@ -82,7 +82,7 @@ query getBatchTasking($callback_id: Int!, $offset: Int!, $fetchLimit: Int!){
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const CallbacksTabsTaskingPanel = ({tabInfo, index, value, onCloseTab, parentMountedRef, me, collapseTaskRequest}) =>{
|
||||
export const CallbacksTabsTaskingPanel = ({tabInfo, index, value, onCloseTab, parentMountedRef, me, collapseTaskRequest, setNewDataForTab}) =>{
|
||||
const [taskLimit, setTaskLimit] = React.useState(20);
|
||||
const [scrollToBottom, setScrollToBottom] = React.useState(false);
|
||||
const [openParametersDialog, setOpenParametersDialog] = React.useState(false);
|
||||
@@ -175,6 +175,9 @@ export const CallbacksTabsTaskingPanel = ({tabInfo, index, value, onCloseTab, pa
|
||||
if(!fetched){
|
||||
setFetched(true);
|
||||
}
|
||||
if(index !== value && fetched){
|
||||
setNewDataForTab((prev) => {return {...prev, [tabInfo.tabID]: true}});
|
||||
}
|
||||
//console.log("new subscription data in CallbacksTabsTasking", subscriptionData);
|
||||
const oldLength = taskingDataRef.current.task.length;
|
||||
const mergedData = data.data.task.reduce( (prev, cur) => {
|
||||
|
||||
@@ -48,7 +48,7 @@ export function CallbacksTabsTaskingConsoleLabel(props){
|
||||
]);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MythicTabLabel label={description} onDragTab={props.onDragTab} me={props.me} {...props} contextMenuOptions={contextMenuOptions}/>
|
||||
<MythicTabLabel label={description} highlight={props.newDataForTab[props.tabInfo.tabID]} onDragTab={props.onDragTab} me={props.me} {...props} contextMenuOptions={contextMenuOptions}/>
|
||||
{openEditDescriptionDialog &&
|
||||
<MythicDialog fullWidth={true} open={openEditDescriptionDialog} onClose={() => {setOpenEditDescriptionDialog(false);}}
|
||||
innerDialog={
|
||||
@@ -82,7 +82,7 @@ query getBatchTasking($callback_id: Int!, $offset: Int!, $fetchLimit: Int!){
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const CallbacksTabsTaskingConsolePanel = ({tabInfo, index, value, onCloseTab, parentMountedRef, me}) =>{
|
||||
export const CallbacksTabsTaskingConsolePanel = ({tabInfo, index, value, onCloseTab, parentMountedRef, me, setNewDataForTab}) =>{
|
||||
const [taskLimit, setTaskLimit] = React.useState(10);
|
||||
const [scrollToBottom, setScrollToBottom] = React.useState(false);
|
||||
const [openParametersDialog, setOpenParametersDialog] = React.useState(false);
|
||||
@@ -175,6 +175,9 @@ export const CallbacksTabsTaskingConsolePanel = ({tabInfo, index, value, onClose
|
||||
if(!fetched){
|
||||
setFetched(true);
|
||||
}
|
||||
if(index !== value && fetched){
|
||||
setNewDataForTab((prev) => {return {...prev, [tabInfo.tabID]: true}});
|
||||
}
|
||||
//console.log("new subscription data in CallbacksTabsTasking", subscriptionData);
|
||||
const oldLength = taskingDataRef.current.task.length;
|
||||
const mergedData = data.data.task.reduce( (prev, cur) => {
|
||||
|
||||
@@ -50,7 +50,7 @@ export function CallbacksTabsTaskingSplitLabel(props){
|
||||
]);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MythicTabLabel label={description} onDragTab={props.onDragTab} me={props.me} {...props} contextMenuOptions={contextMenuOptions}/>
|
||||
<MythicTabLabel label={description} highlight={props.newDataForTab[props.tabInfo.tabID]} onDragTab={props.onDragTab} me={props.me} {...props} contextMenuOptions={contextMenuOptions}/>
|
||||
{openEditDescriptionDialog &&
|
||||
<MythicDialog fullWidth={true} open={openEditDescriptionDialog} onClose={() => {setOpenEditDescriptionDialog(false);}}
|
||||
innerDialog={
|
||||
@@ -82,7 +82,7 @@ query getBatchTasking($callback_id: Int!, $offset: Int!, $fetchLimit: Int!){
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const CallbacksTabsTaskingSplitPanel = ({tabInfo, index, value, onCloseTab, me}) =>{
|
||||
export const CallbacksTabsTaskingSplitPanel = ({tabInfo, index, value, onCloseTab, me, setNewDataForTab}) =>{
|
||||
const [taskLimit, setTaskLimit] = React.useState(30);
|
||||
const [scrollToBottom, setScrollToBottom] = React.useState(false);
|
||||
const [openParametersDialog, setOpenParametersDialog] = React.useState(false);
|
||||
@@ -171,6 +171,9 @@ export const CallbacksTabsTaskingSplitPanel = ({tabInfo, index, value, onCloseTa
|
||||
if(!fetched){
|
||||
setFetched(true);
|
||||
}
|
||||
if(index !== value && fetched){
|
||||
setNewDataForTab((prev) => {return {...prev, [tabInfo.tabID]: true}});
|
||||
}
|
||||
//console.log("new subscription data in CallbacksTabsTasking", subscriptionData);
|
||||
const oldLength = taskingDataRef.current.task.length;
|
||||
const mergedData = data.data.task.reduce( (prev, cur) => {
|
||||
|
||||
@@ -168,6 +168,8 @@ callback(where: {active: {_eq: true}}) {
|
||||
callbackc2profiles {
|
||||
c2profile {
|
||||
name
|
||||
has_logo
|
||||
is_p2p
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,6 +203,8 @@ callback(where: {active: {_eq: true}}) {
|
||||
callbackc2profiles {
|
||||
c2profile {
|
||||
name
|
||||
has_logo
|
||||
is_p2p
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,6 +212,7 @@ callback(where: {active: {_eq: true}}) {
|
||||
id
|
||||
is_p2p
|
||||
name
|
||||
has_logo
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,6 +253,8 @@ callbackgraphedge_stream(batch_size: 100, cursor: {initial_value: {updated_at: $
|
||||
callbackc2profiles {
|
||||
c2profile {
|
||||
name
|
||||
has_logo
|
||||
is_p2p
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,6 +289,8 @@ callbackgraphedge_stream(batch_size: 100, cursor: {initial_value: {updated_at: $
|
||||
callbackc2profiles {
|
||||
c2profile {
|
||||
name
|
||||
has_logo
|
||||
is_p2p
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,6 +298,7 @@ callbackgraphedge_stream(batch_size: 100, cursor: {initial_value: {updated_at: $
|
||||
id
|
||||
is_p2p
|
||||
name
|
||||
has_logo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,13 +298,14 @@ export const ResponseDisplayInteractive = (props) =>{
|
||||
if(page.current === currentPage){
|
||||
const pageCount = Math.max(1, Math.ceil(allData.length / pageSize.current));
|
||||
if(page.current === pageCount -1){
|
||||
console.log("updating pageSize");
|
||||
// we just streamed more data and we're on the latest page, increase pageSize
|
||||
//pageSize.current += Math.abs(allData.length - totalCount);
|
||||
currentPage += 1;
|
||||
}
|
||||
}
|
||||
setAllOutput(allData.slice((currentPage-1)*pageSize.current, currentPage*pageSize.current));
|
||||
if(props.selectAllOutput){
|
||||
setAllOutput(allData);
|
||||
} else {
|
||||
setAllOutput(allData.slice((currentPage-1)*pageSize.current, currentPage*pageSize.current));
|
||||
}
|
||||
setTotalCount(allData.length);
|
||||
}else{
|
||||
let allData = [...rawResponses, ...taskData];
|
||||
@@ -337,6 +338,9 @@ export const ResponseDisplayInteractive = (props) =>{
|
||||
React.useEffect( () => {
|
||||
onSubmitPageChange(1);
|
||||
}, [search]);
|
||||
React.useEffect( () => {
|
||||
onSubmitPageChange(1);
|
||||
}, [props.selectAllOutput]);
|
||||
const onSubmitSearch = React.useCallback( (newSearch) => {
|
||||
setSearch(newSearch);
|
||||
}, []);
|
||||
@@ -431,7 +435,7 @@ export const ResponseDisplayInteractive = (props) =>{
|
||||
|
||||
<InteractivePaginationBar totalCount={totalCount} currentPage={page.current}
|
||||
onSubmitPageChange={onSubmitPageChange} expand={props.expand}
|
||||
pageSize={pageSize.current}/>
|
||||
pageSize={pageSize.current} selectAllOutput={props.selectAllOutput}/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -633,12 +637,12 @@ const InteractiveTaskingBar = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const InteractivePaginationBar = ({totalCount, currentPage, onSubmitPageChange, pageSize, expand}) => {
|
||||
const InteractivePaginationBar = ({totalCount, currentPage, onSubmitPageChange, pageSize, selectAllOutput}) => {
|
||||
const onChangePage = (event, value) => {
|
||||
onSubmitPageChange(value);
|
||||
};
|
||||
const pageCount = Math.max(1, Math.ceil(totalCount / pageSize));
|
||||
if(pageCount < 2){
|
||||
if(pageCount < 2 || selectAllOutput){
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -21,6 +21,7 @@ import {faList, faTrashAlt, faSkullCrossbones, faCamera, faSyringe, faFolder, fa
|
||||
import {Dropdown, DropdownMenuItem} from "../../MythicComponents/MythicNestedMenus";
|
||||
import {GetComputedFontSize} from "../../MythicComponents/MythicSavedUserSetting";
|
||||
import {TableFilterDialog} from "./TableFilterDialog";
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
|
||||
const onCopyToClipboard = (data) => {
|
||||
let result = copyStringToClipboard(data);
|
||||
@@ -407,11 +408,12 @@ const ResponseDisplayTableActionCellButton = ({cellData, callback_id}) => {
|
||||
table={taskingData?.value || {}} callback_id={callback_id} onClose={finishedViewingData} />}
|
||||
/>
|
||||
}
|
||||
<Button size="small" variant={"contained"} color={"info"} ref={dropdownAnchorRef}
|
||||
<Button size="small" variant={"contained"} ref={dropdownAnchorRef}
|
||||
onClick={() => setOpenDropdownButton(true)} disabled={cellData?.button?.disabled || false}
|
||||
startIcon={cellData?.button?.startIcon ? <FontAwesomeIcon icon={getIconName(cellData?.button?.startIcon)} style={{color: cellData?.button?.disabled ? "unset" : getIconColor(theme, cellData?.button?.startIconColor || "")}}/> : null}
|
||||
style={{...actionCellButtonStyle}}
|
||||
>{cellData?.button?.name || " "}</Button>
|
||||
>{cellData?.button?.name || " "} <ArrowDropDownIcon />
|
||||
</Button>
|
||||
<ClickAwayListener onClickAway={handleClose} mouseEvent={"onMouseDown"}>
|
||||
<Dropdown
|
||||
isOpen={dropdownAnchorRef.current}
|
||||
|
||||
@@ -85,6 +85,17 @@ query getCredential($id: Int!){
|
||||
}
|
||||
`;
|
||||
|
||||
const arraysAreDifferent = (a, b) => {
|
||||
if(a.length !== b.length){
|
||||
return true
|
||||
}
|
||||
for(let i = 0; i < a.length; i++){
|
||||
if(a[i] !== b[i]){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function TaskParametersDialogRow(props){
|
||||
const [value, setValue] = React.useState('');
|
||||
const theme = useTheme();
|
||||
@@ -364,7 +375,7 @@ export function TaskParametersDialogRow(props){
|
||||
}
|
||||
}
|
||||
else if(props.type === "LinkInfo"){
|
||||
if(props.choices.length > 0){
|
||||
if(props.choices.length > 0 && value === ''){
|
||||
setChoiceOptions([...props.choices]);
|
||||
onChangeLinkInfo(0);
|
||||
}
|
||||
@@ -378,19 +389,29 @@ export function TaskParametersDialogRow(props){
|
||||
}else{
|
||||
setAgentConnectHost(0);
|
||||
}
|
||||
setAgentConnectHostOptions(props.choices);
|
||||
if(arraysAreDifferent(props.choices, agentConnectHostOptions)){
|
||||
setAgentConnectHostOptions(props.choices);
|
||||
}
|
||||
let payloadNum = 0;
|
||||
if(agentConnectPayload < props.choices[hostNum]["payloads"].length){
|
||||
payloadNum = agentConnectPayload;
|
||||
}
|
||||
setAgentConnectPayload(payloadNum);
|
||||
setAgentConnectPayloadOptions(props.choices[hostNum]["payloads"]);
|
||||
if(agentConnectPayload !== payloadNum){
|
||||
setAgentConnectPayload(payloadNum);
|
||||
}
|
||||
if(arraysAreDifferent(props.choices[hostNum]['payloads'], agentConnectPayloadOptions)){
|
||||
setAgentConnectPayloadOptions(props.choices[hostNum]["payloads"]);
|
||||
}
|
||||
if(props.choices[hostNum]["payloads"].length > 0){
|
||||
//setAgentConnectPayload(0);
|
||||
if(props.choices[hostNum]["payloads"][payloadNum]["c2info"].length > 0){
|
||||
setAgentConnectC2ProfileOptions(props.choices[hostNum]["payloads"][payloadNum]["c2info"]);
|
||||
if(arraysAreDifferent(props.choices[hostNum]["payloads"][payloadNum]["c2info"], agentConnectC2ProfileOptions)){
|
||||
setAgentConnectC2ProfileOptions(props.choices[hostNum]["payloads"][payloadNum]["c2info"]);
|
||||
}
|
||||
//setAgentConnectC2Profile(0);
|
||||
onChangeAgentConnect(agentConnectHost, payloadNum, 0);
|
||||
if(agentConnectHost !== hostNum || agentConnectPayload !== payloadNum){
|
||||
onChangeAgentConnect(hostNum, payloadNum, 0);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
snackActions.warning("Mythic knows of no host with a P2P payload. Please add one.");
|
||||
@@ -441,7 +462,9 @@ export function TaskParametersDialogRow(props){
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [props.choices, props.default_value, props.type, props.value, setBoolValue, value]);
|
||||
}, [props.choices, props.default_value, props.type, props.value, setBoolValue,
|
||||
value, agentConnectHost, agentConnectPayload, agentConnectPayloadOptions, agentConnectHostOptions,
|
||||
agentConnectC2ProfileOptions]);
|
||||
const onChangeAgentConnect = (host_index, payload_index, c2_index) => {
|
||||
const c2profileparameters = props.choices[host_index]["payloads"][payload_index]["c2info"][c2_index].parameters.reduce( (prev, opt) => {
|
||||
return {...prev, [opt.name]: opt.value}
|
||||
@@ -1007,7 +1030,7 @@ export function TaskParametersDialogRow(props){
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell>Payload</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>Payload / Callback</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<FormControl style={{width: "100%"}}>
|
||||
<Select
|
||||
|
||||
@@ -16,6 +16,7 @@ import {ParseForDisplay} from "../Payloads/DetailedPayloadTable";
|
||||
import Paper from '@mui/material/Paper';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import {getModifiedC2Params} from "./Step4C2Profiles";
|
||||
import { Backdrop, CircularProgress } from '@mui/material';
|
||||
|
||||
|
||||
const GET_Payload_Types = gql`
|
||||
@@ -37,11 +38,23 @@ query getPayloadTypesQuery {
|
||||
|
||||
export const GetPayloads = gql`
|
||||
query payloads($payloadType: String!, $os: String!) {
|
||||
payload(where: {auto_generated: {_eq: false}, os: {_eq: $os}, payloadtype: {name: {_eq: $payloadType}}, build_phase: {_eq: "success"}, deleted: {_eq: false}}, order_by: {id: desc}) {
|
||||
payload(where: {auto_generated: {_eq: false}, os: {_eq: $os}, payloadtype: {name: {_eq: $payloadType}}, deleted: {_eq: false}}, order_by: {id: desc}) {
|
||||
id
|
||||
description
|
||||
uuid
|
||||
creation_time
|
||||
build_phase
|
||||
payload_build_steps(order_by: {step_number: asc}) {
|
||||
step_name
|
||||
step_number
|
||||
step_success
|
||||
step_skip
|
||||
start_time
|
||||
end_time
|
||||
step_stdout
|
||||
step_stderr
|
||||
id
|
||||
}
|
||||
filemetum {
|
||||
agent_file_id
|
||||
filename_text
|
||||
@@ -153,6 +166,7 @@ query getPayloadTypesBuildParametersQuery($payload_id: Int!) {
|
||||
|
||||
export function Step1SelectOS(props){
|
||||
const [os, setOS] = React.useState('');
|
||||
const [openBackdrop, setOpenBackdrop] = React.useState(true);
|
||||
const [payloadtypeData, setPayloadtypeData] = React.useState({});
|
||||
const [payloadtypesPerOS, setPayloadtypesPerOS] = React.useState({});
|
||||
const [C2PerOS, setC2PerOS] = React.useState({});
|
||||
@@ -219,11 +233,16 @@ export function Step1SelectOS(props){
|
||||
.catch((data) => {console.log(data)})
|
||||
}, [props.prevData, props.first])
|
||||
React.useEffect( () => {
|
||||
setOpenBackdrop(true);
|
||||
getPayloads({variables: {payloadType: selectedPayloadType, os}})
|
||||
.then(({data}) => {
|
||||
setPayloadOptions(data.payload);
|
||||
setOpenBackdrop(false);
|
||||
})
|
||||
.catch(({data}) => console.log(data));
|
||||
.catch(({data}) => {
|
||||
console.log(data);
|
||||
setOpenBackdrop(false);
|
||||
});
|
||||
}, [selectedPayloadType, os]);
|
||||
|
||||
const finished = (clearNextPrevious) => {
|
||||
@@ -437,13 +456,21 @@ export function Step1SelectOS(props){
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
{props.first &&
|
||||
<StartFromExistingPayloadOrStartFresh first={props.first}
|
||||
last={props.last}
|
||||
canceled={canceled}
|
||||
onSelectedPayload={onSelectedPayload}
|
||||
payloadOptions={payloadOptions}
|
||||
onStartFresh={onStartFresh}
|
||||
/>
|
||||
<div style={{flexGrow: 1, overflowY: "auto", position: "relative"}}>
|
||||
{openBackdrop &&
|
||||
<Backdrop open={openBackdrop} onClick={()=>{setOpenBackdrop(false);}} style={{zIndex: 2000, position: "absolute"}}>
|
||||
<CircularProgress color="inherit" disableShrink />
|
||||
</Backdrop>
|
||||
}
|
||||
<StartFromExistingPayloadOrStartFresh first={props.first}
|
||||
last={props.last}
|
||||
canceled={canceled}
|
||||
onSelectedPayload={onSelectedPayload}
|
||||
payloadOptions={payloadOptions}
|
||||
onStartFresh={onStartFresh}
|
||||
/>
|
||||
</div>
|
||||
|
||||
}
|
||||
{!props.first &&
|
||||
<ConfigureBuildParameters os={os} selectedPayloadType={selectedPayloadType}
|
||||
@@ -659,7 +686,7 @@ export const GetGroupedParameters = ({buildParameters, os, c2_name}) => {
|
||||
if(buildParameters[i].group_name === groupedData[j].name){
|
||||
// only add the parameter if it doesn't meet a hide_condition
|
||||
let should_hide = false;
|
||||
if(buildParameters[i]?.supported_os?.length || 0 > 0){
|
||||
if((buildParameters[i]?.supported_os?.length || 0) > 0){
|
||||
if(!buildParameters[i]?.supported_os?.includes(os)){
|
||||
should_hide = true;
|
||||
}
|
||||
@@ -669,17 +696,22 @@ export const GetGroupedParameters = ({buildParameters, os, c2_name}) => {
|
||||
if(buildParameters[l].name === buildParameters[i].hide_conditions[k].name){
|
||||
switch(buildParameters[i].hide_conditions[k].operand){
|
||||
case "eq":
|
||||
if(buildParameters[i].hide_conditions[k].value === buildParameters[l].value){
|
||||
if(String(buildParameters[i].hide_conditions[k].value) === String(buildParameters[l].value)){
|
||||
should_hide = true;
|
||||
}
|
||||
break;
|
||||
case "neq":
|
||||
if(buildParameters[i].hide_conditions[k].value !== buildParameters[l].value){
|
||||
if(String(buildParameters[i].hide_conditions[k].value) !== String(buildParameters[l].value)){
|
||||
should_hide = true;
|
||||
}
|
||||
break;
|
||||
case "in":
|
||||
if(buildParameters[i].hide_conditions[k].value.includes(buildParameters[l].value)){
|
||||
if(buildParameters[i].hide_conditions[k].choices.includes(buildParameters[l].value)){
|
||||
should_hide = true;
|
||||
}
|
||||
break;
|
||||
case "nin":
|
||||
if(!buildParameters[i].hide_conditions[k].choices.includes(buildParameters[l].value)){
|
||||
should_hide = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -102,6 +102,9 @@ export const getModifiedC2Params = (c2, c2profileparameters, buildOptions, use_s
|
||||
if(use_supplied_values){
|
||||
configuredParam = {...param}
|
||||
}
|
||||
if(buildOptions.c2_parameter_deviations === null){
|
||||
return [...prev, {...configuredParam}];
|
||||
}
|
||||
if(buildOptions.c2_parameter_deviations[c2.name] === undefined){
|
||||
return [...prev, {...configuredParam}];
|
||||
}
|
||||
@@ -117,7 +120,12 @@ export const getModifiedC2Params = (c2, c2profileparameters, buildOptions, use_s
|
||||
configuredParam.trackedValue = c2Config.default_value;
|
||||
configuredParam.initialValue = c2Config.default_value;
|
||||
}
|
||||
if(c2Config.choices !== undefined){
|
||||
if(c2Config.dictionary_choices !== undefined){
|
||||
configuredParam.choices = c2Config.dictionary_choices;
|
||||
configuredParam.value = configuredParam.choices;
|
||||
configuredParam.trackedValue = configuredParam.choices;
|
||||
configuredParam.initialValue = configuredParam.choices;
|
||||
}else if(c2Config.choices !== undefined){
|
||||
configuredParam.choices = c2Config.choices;
|
||||
if(!configuredParam.choices.includes(configuredParam.default_value)){
|
||||
if(configuredParam.choices.length > 0){
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import {useQuery, gql} from '@apollo/client';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Select from '@mui/material/Select';
|
||||
import { CreatePayloadNavigationButtons} from './CreatePayloadNavigationButtons';
|
||||
@@ -8,7 +7,7 @@ import Typography from '@mui/material/Typography';
|
||||
import {snackActions} from '../../utilities/Snackbar';
|
||||
import {useMythicLazyQuery} from "../../utilities/useMythicLazyQuery";
|
||||
import {getDefaultChoices, getDefaultValueForType, getSavedToType} from "../CreatePayload/Step2SelectPayloadType";
|
||||
import {getModifiedC2Params} from "../CreatePayload/Step4C2Profiles";
|
||||
import { Backdrop, CircularProgress } from '@mui/material';
|
||||
import {MythicAgentSVGIcon} from "../../MythicComponents/MythicAgentSVGIcon";
|
||||
import {
|
||||
ConfigureBuildParameters,
|
||||
@@ -73,6 +72,7 @@ query getPayloadTypesBuildParametersQuery($payload_id: Int!) {
|
||||
|
||||
export function Step1SelectOS(props){
|
||||
const [os, setOS] = React.useState('');
|
||||
const [openBackdrop, setOpenBackdrop] = React.useState(true);
|
||||
const [payloadtypeData, setPayloadtypeData] = React.useState({});
|
||||
const [payloadtypesPerOS, setPayloadtypesPerOS] = React.useState({});
|
||||
const [osOptions, setOSOptions] = React.useState([]);
|
||||
@@ -125,11 +125,16 @@ export function Step1SelectOS(props){
|
||||
.catch((data) => {console.log(data)})
|
||||
}, [props.prevData, props.first])
|
||||
React.useEffect( () => {
|
||||
setOpenBackdrop(true);
|
||||
getPayloads({variables: {payloadType: selectedPayloadType, os}})
|
||||
.then(({data}) => {
|
||||
setPayloadOptions(data.payload);
|
||||
setOpenBackdrop(false);
|
||||
})
|
||||
.catch(({data}) => console.log(data));
|
||||
.catch(({data}) => {
|
||||
console.log(data);
|
||||
setOpenBackdrop(false);
|
||||
});
|
||||
}, [selectedPayloadType, os]);
|
||||
|
||||
const finished = (clearNextPrevious) => {
|
||||
@@ -296,13 +301,21 @@ export function Step1SelectOS(props){
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
{props.first &&
|
||||
<StartFromExistingPayloadOrStartFresh first={props.first}
|
||||
last={props.last}
|
||||
canceled={canceled}
|
||||
onSelectedPayload={onSelectedPayload}
|
||||
payloadOptions={payloadOptions}
|
||||
onStartFresh={onStartFresh}
|
||||
/>
|
||||
<div style={{flexGrow: 1, overflowY: "auto", position: "relative"}}>
|
||||
{openBackdrop &&
|
||||
<Backdrop open={openBackdrop} onClick={()=>{setOpenBackdrop(false);}} style={{zIndex: 2000, position: "absolute"}}>
|
||||
<CircularProgress color="inherit" disableShrink />
|
||||
</Backdrop>
|
||||
}
|
||||
<StartFromExistingPayloadOrStartFresh first={props.first}
|
||||
last={props.last}
|
||||
canceled={canceled}
|
||||
onSelectedPayload={onSelectedPayload}
|
||||
payloadOptions={payloadOptions}
|
||||
onStartFresh={onStartFresh}
|
||||
/>
|
||||
</div>
|
||||
|
||||
}
|
||||
{!props.first &&
|
||||
<ConfigureBuildParameters os={os} selectedPayloadType={selectedPayloadType}
|
||||
|
||||
@@ -19,8 +19,10 @@ import InfoIconOutline from '@mui/icons-material/InfoOutlined';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import {b64DecodeUnicode} from '../Callbacks/ResponseDisplay';
|
||||
import {MythicAgentSVGIcon} from "../../MythicComponents/MythicAgentSVGIcon";
|
||||
import {ConfigureBuildParameters, StartFromExistingPayloadOrStartFresh} from "../CreatePayload/Step1SelectOS";
|
||||
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
|
||||
import { Backdrop, CircularProgress } from '@mui/material';
|
||||
import {PayloadsTableRowBuildStatus} from "../Payloads/PayloadsTableRowBuildStatus";
|
||||
import {PayloadsTableRowBuildProgress} from "../Payloads/PayloadsTableRowBuildProgress";
|
||||
|
||||
|
||||
const PREFIX = 'Step3SelectPayload';
|
||||
|
||||
@@ -63,6 +65,18 @@ query getWrappablePayloads($payloadType: Int!) {
|
||||
description
|
||||
uuid
|
||||
creation_time
|
||||
build_phase
|
||||
payload_build_steps(order_by: {step_number: asc}) {
|
||||
step_name
|
||||
step_number
|
||||
step_success
|
||||
step_skip
|
||||
start_time
|
||||
end_time
|
||||
step_stdout
|
||||
step_stderr
|
||||
id
|
||||
}
|
||||
filemetum {
|
||||
agent_file_id
|
||||
filename_text
|
||||
@@ -77,6 +91,7 @@ query getWrappablePayloads($payloadType: Int!) {
|
||||
|
||||
export function Step3SelectPayload(props){
|
||||
const [payloadOptions, setPayloadOptions] = React.useState([]);
|
||||
const [openBackdrop, setOpenBackdrop] = React.useState(true);
|
||||
useQuery(GET_Payload_Types, {fetchPolicy: "network-only", variables: {payloadType: props.buildOptions["payload_type_id"]},
|
||||
onCompleted: (data) => {
|
||||
if(data.payloadtype_by_pk.wrap_these_payload_types.length > 0){
|
||||
@@ -87,6 +102,7 @@ export function Step3SelectPayload(props){
|
||||
}
|
||||
}
|
||||
setPayloadOptions(options);
|
||||
setOpenBackdrop(false);
|
||||
}else{
|
||||
snackActions.warning("No supported payload for that wrapper");
|
||||
}
|
||||
@@ -155,7 +171,12 @@ export function Step3SelectPayload(props){
|
||||
<Typography variant={"p"} style={{fontWeight: 600}}>
|
||||
1. Select Payload to Include
|
||||
</Typography>
|
||||
<div style={{flexGrow: 1, overflowY: "auto"}}>
|
||||
<div style={{flexGrow: 1, overflowY: "auto", position: "relative"}}>
|
||||
{openBackdrop &&
|
||||
<Backdrop open={openBackdrop} onClick={()=>{setOpenBackdrop(false);}} style={{zIndex: 2000, position: "absolute"}}>
|
||||
<CircularProgress color="inherit" disableShrink />
|
||||
</Backdrop>
|
||||
}
|
||||
<PayloadSelect payloadOptions={payloadOptions} first={props.first} last={props.last}
|
||||
canceled={canceled} finished={finished}/>
|
||||
</div>
|
||||
@@ -184,6 +205,7 @@ export function PayloadSelect(props) {
|
||||
<TableCell style={{width: "6rem"}}> Select</TableCell>
|
||||
<TableCell style={{width: "15rem"}}>Timestamp</TableCell>
|
||||
<TableCell>File</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell style={{width: "5rem"}}>Details</TableCell>
|
||||
</TableRow>
|
||||
@@ -218,6 +240,10 @@ export function PayloadsTableRow(props){
|
||||
</TableCell>
|
||||
<TableCell>{toLocalTime(props.payload.creation_time, me.user.view_utc_time)}</TableCell>
|
||||
<TableCell>{b64DecodeUnicode(props.payload.filemetum.filename_text)}</TableCell>
|
||||
<TableCell>
|
||||
<PayloadsTableRowBuildStatus {...props.payload} />
|
||||
<PayloadsTableRowBuildProgress {...props.payload} />
|
||||
</TableCell>
|
||||
<TableCell>{props.payload.description}</TableCell>
|
||||
<TableCell>
|
||||
<IconButton size="small" color="info" onClick={() => setOpenDetailedView(true)}>
|
||||
|
||||
@@ -4,9 +4,9 @@ import {EventFeedTable} from './EventFeedTable';
|
||||
import {snackActions} from '../../utilities/Snackbar';
|
||||
import {alertCount} from "../../../cache";
|
||||
|
||||
const GET_Event_Feed = gql`
|
||||
const GET_Event_Feed_Warnings = gql`
|
||||
query GetOperationEventLogs($offset: Int!, $limit: Int!, $search: String!, $level: String!, $resolved: Boolean!) {
|
||||
operationeventlog(where: {deleted: {_eq: false}, message: {_ilike: $search}, level: {_like: $level}, resolved: {_eq: $resolved}}, order_by: {id: desc}, limit: $limit, offset: $offset) {
|
||||
operationeventlog(where: {deleted: {_eq: false}, message: {_ilike: $search}, level: {_like: $level}, resolved: {_eq: $resolved}, warning: {_eq: true}}, order_by: {id: desc}, limit: $limit, offset: $offset) {
|
||||
id
|
||||
level
|
||||
message
|
||||
@@ -14,17 +14,18 @@ query GetOperationEventLogs($offset: Int!, $limit: Int!, $search: String!, $leve
|
||||
timestamp
|
||||
count
|
||||
source
|
||||
warning
|
||||
}
|
||||
operationeventlog_aggregate(where: {deleted: {_eq: false}, message: {_ilike: $search}, level: {_like: $level}, resolved: {_eq: $resolved}}) {
|
||||
operationeventlog_aggregate(where: {deleted: {_eq: false}, message: {_ilike: $search}, level: {_like: $level}, resolved: {_eq: $resolved}, warning: {_eq: true}}) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const GET_Event_Feed_No_Resolved = gql`
|
||||
const GET_Event_Feed_No_Warnings = gql`
|
||||
query GetOperationEventLogs($offset: Int!, $limit: Int!, $search: String!, $level: String!) {
|
||||
operationeventlog(where: {deleted: {_eq: false}, message: {_ilike: $search}, level: {_like: $level}}, order_by: {id: desc}, limit: $limit, offset: $offset) {
|
||||
operationeventlog(where: {deleted: {_eq: false}, message: {_ilike: $search}, level: {_ilike: $level}}, order_by: {id: desc}, limit: $limit, offset: $offset) {
|
||||
id
|
||||
level
|
||||
message
|
||||
@@ -32,8 +33,9 @@ query GetOperationEventLogs($offset: Int!, $limit: Int!, $search: String!, $leve
|
||||
timestamp
|
||||
count
|
||||
source
|
||||
warning
|
||||
}
|
||||
operationeventlog_aggregate(where: {deleted: {_eq: false}, message: {_ilike: $search}, level: {_like: $level}}) {
|
||||
operationeventlog_aggregate(where: {deleted: {_eq: false}, message: {_ilike: $search}, level: {_ilike: $level}}) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
@@ -50,6 +52,7 @@ subscription GetOperationEventLogs($fromNow: timestamp!) {
|
||||
timestamp
|
||||
count
|
||||
source
|
||||
warning
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -63,15 +66,16 @@ mutation UpdateResolutionOperationEventLog($id: Int!, $resolved: Boolean!) {
|
||||
`;
|
||||
const Update_Level = gql`
|
||||
mutation UpdateLevelOperationEventLog($id: Int!) {
|
||||
update_operationeventlog_by_pk(pk_columns: {id: $id}, _set: {level: "warning"}) {
|
||||
update_operationeventlog_by_pk(pk_columns: {id: $id}, _set: {warning: true, resolved: false}) {
|
||||
id
|
||||
level
|
||||
warning
|
||||
resolved
|
||||
}
|
||||
}
|
||||
`;
|
||||
const Update_ResolveViewableErrors = gql`
|
||||
mutation UpdateResolveViewableErrorsOperationEventLog($ids: [Int]!) {
|
||||
update_operationeventlog(where:{id: {_in: $ids}}, _set: {resolved: true}) {
|
||||
update_operationeventlog(where:{id: {_in: $ids}, warning: {_eq: true}}, _set: {resolved: true}) {
|
||||
returning{
|
||||
id
|
||||
resolved
|
||||
@@ -81,7 +85,7 @@ mutation UpdateLevelOperationEventLog($id: Int!) {
|
||||
`;
|
||||
const Update_ResolveAllErrors = gql`
|
||||
mutation UpdateResolveAllErrorsOperationEventLog {
|
||||
update_operationeventlog(where: {level: {_eq: "warning"}, resolved: {_eq: false}}, _set: {resolved: true}) {
|
||||
update_operationeventlog(where: {resolved: {_eq: false}, warning: {_eq: true}}, _set: {resolved: true}) {
|
||||
returning{
|
||||
id
|
||||
resolved
|
||||
@@ -115,11 +119,11 @@ export function EventFeed({}){
|
||||
}
|
||||
});
|
||||
|
||||
const [getMoreTasking] = useLazyQuery(GET_Event_Feed, {
|
||||
const [getMoreEventFeedWithWarning] = useLazyQuery(GET_Event_Feed_Warnings, {
|
||||
onError: data => {
|
||||
console.error(data)
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
fetchPolicy: "no-cache",
|
||||
onCompleted: (data) => {
|
||||
snackActions.dismiss();
|
||||
let tempPageData = {...pageData};
|
||||
@@ -130,11 +134,11 @@ export function EventFeed({}){
|
||||
setOperationEventLog(newEventLog);
|
||||
}
|
||||
});
|
||||
const [getMoreTaskingNoResolved] = useLazyQuery(GET_Event_Feed_No_Resolved, {
|
||||
const [getMoreEventFeed] = useLazyQuery(GET_Event_Feed_No_Warnings, {
|
||||
onError: data => {
|
||||
console.error(data)
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
fetchPolicy: "no-cache",
|
||||
onCompleted: (data) => {
|
||||
snackActions.dismiss();
|
||||
let tempPageData = {...pageData};
|
||||
@@ -162,7 +166,7 @@ export function EventFeed({}){
|
||||
const updatedMessage = data.update_operationeventlog_by_pk;
|
||||
const updatedMessages = operationeventlog.map( (log) => {
|
||||
if(log.id === updatedMessage.id){
|
||||
return {...log, level: updatedMessage.level};
|
||||
return {...log, warning: updatedMessage.warning, resolved: updatedMessage.resolved};
|
||||
}
|
||||
return log;
|
||||
});
|
||||
@@ -230,19 +234,19 @@ export function EventFeed({}){
|
||||
localLevel = "%_%";
|
||||
} else if(localLevel === "warning (unresolved)"){
|
||||
localResolved = false;
|
||||
localLevel = "warning";
|
||||
localLevel = "%_%";
|
||||
} else if(localLevel === "warning (resolved)"){
|
||||
localResolved = true;
|
||||
localLevel = "warning";
|
||||
localLevel = "%_%";
|
||||
}
|
||||
if(localResolved === undefined){
|
||||
getMoreTaskingNoResolved({variables: {offset: (value - 1) * pageData.fetchLimit,
|
||||
getMoreEventFeed({variables: {offset: (value - 1) * pageData.fetchLimit,
|
||||
limit: pageData.fetchLimit,
|
||||
search: localSearch,
|
||||
level: localLevel
|
||||
}})
|
||||
} else {
|
||||
getMoreTasking({variables: {offset: (value - 1) * pageData.fetchLimit,
|
||||
getMoreEventFeedWithWarning({variables: {offset: (value - 1) * pageData.fetchLimit,
|
||||
limit: pageData.fetchLimit,
|
||||
search: localSearch,
|
||||
level: localLevel,
|
||||
@@ -252,13 +256,9 @@ export function EventFeed({}){
|
||||
|
||||
}
|
||||
React.useEffect( () => {
|
||||
if( alertCount() > 0){
|
||||
} else {
|
||||
getMoreTaskingNoResolved({variables: {offset: 0,
|
||||
limit: pageData.fetchLimit, search: "%_%", level: "%_%"
|
||||
}});
|
||||
if( alertCount() === 0){
|
||||
getMoreEventFeed({variables: {offset: 0, limit: pageData.fetchLimit, search: "%_%", level: "%_%"}});
|
||||
}
|
||||
|
||||
}, [])
|
||||
const resolveViewableErrors = useCallback( () => {
|
||||
snackActions.info("Resolving Errors...");
|
||||
|
||||
@@ -32,7 +32,7 @@ export function EventFeedTable(props){
|
||||
const [search, setSearch] = React.useState("");
|
||||
const [level, setLevel] = React.useState("info");
|
||||
const levelOptions = [
|
||||
"All Levels", "warning (unresolved)", "warning (resolved)", "info", "debug", "api"
|
||||
"All Levels", "warning (unresolved)", "warning (resolved)", "info", "debug", "api", "auth", "agent_message"
|
||||
];
|
||||
|
||||
const handleSearchValueChange = (name, value, error) => {
|
||||
|
||||
@@ -33,7 +33,7 @@ const StyledListItem = styled(ListItem)((
|
||||
const GetPreAdornment = ({message}) => {
|
||||
const theme = useTheme();
|
||||
const getColor = React.useCallback(() => {
|
||||
if(message.level === "warning"){
|
||||
if(message.warning){
|
||||
if(message.resolved){
|
||||
return theme.palette.success.main;
|
||||
} else {
|
||||
@@ -41,9 +41,9 @@ const GetPreAdornment = ({message}) => {
|
||||
}
|
||||
}
|
||||
return theme.palette.info.main;
|
||||
}, [theme, message.level, message.resolved]);
|
||||
}, [theme, message.level, message.resolved, message.warning]);
|
||||
const getSymbol = React.useCallback(() => {
|
||||
if(message.level === "warning"){
|
||||
if(message.warning){
|
||||
if(message.resolved){
|
||||
return "+";
|
||||
} else {
|
||||
@@ -51,7 +51,7 @@ const GetPreAdornment = ({message}) => {
|
||||
}
|
||||
}
|
||||
return "*";
|
||||
}, [theme, message.level, message.resolved]);
|
||||
}, [theme, message.level, message.resolved, message.warning]);
|
||||
return (
|
||||
<>
|
||||
<p style={{display: "inline-block", fontWeight: "bold", color: getColor(), margin: "0 0 0 0"}}>[</p>
|
||||
@@ -97,7 +97,7 @@ export function EventFeedTableEvents(props){
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<EventFeedTableEventsActions id={props.id} level={props.level}
|
||||
<EventFeedTableEventsActions id={props.id} level={props.level} warning={props.warning}
|
||||
onUpdateResolution={props.onUpdateResolution}
|
||||
onUpdateLevel={props.onUpdateLevel}
|
||||
resolved={props.resolved}/>
|
||||
|
||||
@@ -32,9 +32,9 @@ export function EventFeedTableEventsActions(props){
|
||||
</IconButton>
|
||||
<Menu elevation={5} id={"eventmenu" + props.id} anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleClose} >
|
||||
{
|
||||
props.level === "warning" && props.resolved ? (
|
||||
props.warning && props.resolved ? (
|
||||
<MenuItem onClick={onUpdateResolution}>Unresolve</MenuItem>
|
||||
) : ( props.level === "warning" && !props.resolved ? (
|
||||
) : ( props.warning && !props.resolved ? (
|
||||
<MenuItem onClick={onUpdateResolution}>Resolve</MenuItem>
|
||||
) : (
|
||||
<MenuItem onClick={onUpdateLevel}>Make Warning</MenuItem>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {ResponseDisplayPlaintext} from "../Callbacks/ResponseDisplayPlaintext";
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import {UploadEventFile} from "../../MythicComponents/MythicFileUpload";
|
||||
|
||||
const updateEventGroupMutation = gql(`
|
||||
mutation updateEventGroupMutation($eventgroup_id: Int!, $updated_config: String) {
|
||||
@@ -28,7 +29,7 @@ query exportWorkflow($eventgroup_id: Int!, $include_steps: Boolean!, $output_for
|
||||
}
|
||||
`)
|
||||
const outputFormatOptions = ["yaml", "json", "toml"];
|
||||
export function EventGroupTableEditDialog({onClose, selectedEventGroup}) {
|
||||
export function EventGroupTableEditDialog({onClose, selectedEventGroup, includeSteps = false}) {
|
||||
const workflowRef = React.useRef("");
|
||||
const [outputFormat, setOutputFormat] = React.useState("yaml");
|
||||
const [fetchedData, setFetchedData] = React.useState(false);
|
||||
@@ -47,6 +48,18 @@ export function EventGroupTableEditDialog({onClose, selectedEventGroup}) {
|
||||
snackActions.error("Failed to update");
|
||||
}
|
||||
});
|
||||
const submitAsFile = async (evt) => {
|
||||
let blob = new Blob([workflowRef.current], { type: 'text/plain' });
|
||||
let file = new File([blob], "manual_eventing.yaml", {type: "text/plain"});
|
||||
let uploadStatus = await UploadEventFile(file, "New Manual Eventing Workflow");
|
||||
if(!uploadStatus){
|
||||
snackActions.error("Failed to upload file");
|
||||
}
|
||||
if(uploadStatus.status === "error"){
|
||||
snackActions.error(uploadStatus.error);
|
||||
}
|
||||
onClose();
|
||||
}
|
||||
const [getExportedWorkflow] = useLazyQuery(getExportWorkflow, {
|
||||
fetchPolicy: "no-cache",
|
||||
onCompleted: (data) => {
|
||||
@@ -66,9 +79,9 @@ export function EventGroupTableEditDialog({onClose, selectedEventGroup}) {
|
||||
React.useEffect( () => {
|
||||
getExportedWorkflow({variables: {
|
||||
eventgroup_id: selectedEventGroup.id,
|
||||
include_steps: false,
|
||||
include_steps: includeSteps,
|
||||
output_format: outputFormat}})
|
||||
}, [outputFormat, selectedEventGroup.id]);
|
||||
}, [outputFormat, selectedEventGroup.id, includeSteps]);
|
||||
const onUpdateClick = () => {
|
||||
UpdateEventGroupMutation({variables: {eventgroup_id: selectedEventGroup.id, updated_config: workflowRef.current}})
|
||||
}
|
||||
@@ -104,8 +117,8 @@ export function EventGroupTableEditDialog({onClose, selectedEventGroup}) {
|
||||
<Button onClick={onClose} variant="contained" color="primary">
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={onUpdateClick} variant="contained" color="success">
|
||||
Update
|
||||
<Button onClick={includeSteps ? submitAsFile : onUpdateClick} variant="contained" color="success">
|
||||
{includeSteps ? "Create New Workflow": "Update"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -17,8 +17,7 @@ import {
|
||||
} from "./EventStepRender";
|
||||
import OpenInNewTwoToneIcon from '@mui/icons-material/OpenInNewTwoTone';
|
||||
import PermMediaTwoToneIcon from '@mui/icons-material/PermMediaTwoTone';
|
||||
import {PreviewFileMediaDialog} from "../Search/PreviewFileMedia";
|
||||
import {b64DecodeUnicode} from "../Callbacks/ResponseDisplay";
|
||||
import AddCircleIcon from '@mui/icons-material/AddCircle';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
@@ -34,7 +33,7 @@ import PlayCircleFilledTwoToneIcon from '@mui/icons-material/PlayCircleFilledTwo
|
||||
import RuleTwoToneIcon from '@mui/icons-material/RuleTwoTone';
|
||||
import ChecklistRtlTwoToneIcon from '@mui/icons-material/ChecklistRtlTwoTone';
|
||||
import {EventGroupTableRunAsDialog} from "./EventApprovalDialog";
|
||||
import AttachmentIcon from '@mui/icons-material/Attachment';
|
||||
import AttachFileIcon from '@mui/icons-material/AttachFile';
|
||||
import Badge from '@mui/material/Badge';
|
||||
import {EventFileManageDialog} from "./EventFileManageDialog";
|
||||
import SpellcheckIcon from '@mui/icons-material/Spellcheck';
|
||||
@@ -67,7 +66,7 @@ mutation eventingManualTrigger($eventgroup_id: Int!){
|
||||
}
|
||||
}
|
||||
`)
|
||||
export function EventGroupTable({selectedEventGroup, me}) {
|
||||
export function EventGroupTable({selectedEventGroup, me, showInstances, showGraph}) {
|
||||
const [openEventStepRender, setOpenEventStepRender] = React.useState(false);
|
||||
const [openEnvView, setOpenEnvView] = React.useState(false);
|
||||
const [openTriggerDataView, setOpenTriggerDataView] = React.useState(false);
|
||||
@@ -302,12 +301,12 @@ export function EventGroupTable({selectedEventGroup, me}) {
|
||||
<OpenInNewTwoToneIcon color={"info"} style={{cursor: "pointer", marginRight: "5px"}}/>
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"View Original File"}>
|
||||
<MythicStyledTooltip title={"Create New Workflow From This One"}>
|
||||
<IconButton onClick={() => {
|
||||
setOpenFileView(true);
|
||||
}} size={"small"} color={"secondary"} style={{}}>
|
||||
<PermMediaTwoToneIcon color={"info"} style={{cursor: "pointer", marginRight: "5px"}}/>
|
||||
|
||||
<AddCircleIcon color={"success"} style={{marginLeft: "-8px", marginTop: "7px", backgroundColor: "white", borderRadius: "10px"}} fontSize={"small"} />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Manage Associated Files"}>
|
||||
@@ -315,7 +314,7 @@ export function EventGroupTable({selectedEventGroup, me}) {
|
||||
setOpenFileManageView(true);
|
||||
}} size={"small"} variant={"outlined"} color={"secondary"} style={{}}>
|
||||
<Badge badgeContent={selectedEventGroup.filemeta.length} color="secondary">
|
||||
<AttachmentIcon color={"info"} style={{cursor: "pointer", marginRight: "5px"}}/>
|
||||
<AttachFileIcon color={"info"} style={{cursor: "pointer", marginRight: "5px"}}/>
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
@@ -347,11 +346,11 @@ export function EventGroupTable({selectedEventGroup, me}) {
|
||||
|
||||
</div>
|
||||
}
|
||||
<RenderSteps selectedInstanceID={selectedInstanceID} selectedEventGroup={selectedEventGroup} />
|
||||
<EventGroupInstances setSelectedInstance={setSelectedInstanceID}
|
||||
{showGraph && <RenderSteps selectedInstanceID={selectedInstanceID} selectedEventGroup={selectedEventGroup} />}
|
||||
{showInstances && <EventGroupInstances setSelectedInstance={setSelectedInstanceID}
|
||||
selectedInstanceID={selectedInstanceID}
|
||||
foundQueryInstanceRef={foundQueryInstanceRef}
|
||||
selectedEventGroup={selectedEventGroup} me={me}/>
|
||||
selectedEventGroup={selectedEventGroup} me={me}/>}
|
||||
{openEventStepRender &&
|
||||
<MythicDialog fullWidth={true} maxWidth="xl" open={openEventStepRender}
|
||||
onClose={() => {
|
||||
@@ -374,19 +373,16 @@ export function EventGroupTable({selectedEventGroup, me}) {
|
||||
/>
|
||||
}
|
||||
{openFileView &&
|
||||
<MythicDialog fullWidth={true} maxWidth="xl" open={openFileView}
|
||||
onClose={(e) => {
|
||||
<MythicDialog fullWidth={true} maxWidth="lg" open={openFileView}
|
||||
onClose={() => {
|
||||
setOpenFileView(false);
|
||||
}}
|
||||
innerDialog={<PreviewFileMediaDialog
|
||||
agent_file_id={selectedEventGroup.filemetum.agent_file_id}
|
||||
filename={b64DecodeUnicode(selectedEventGroup.filemetum.filename_text)}
|
||||
onClose={(e) => {
|
||||
setOpenFileView(false);
|
||||
}}/>}
|
||||
innerDialog={<EventGroupTableEditDialog onClose={() => {
|
||||
setOpenFileView(false);
|
||||
}} me={me} selectedEventGroup={selectedEventGroup} includeSteps={true} />}
|
||||
/>
|
||||
}
|
||||
{openEnvView && <MythicDialog fullWidth={true} maxWidth="md" open={openEnvView}
|
||||
{openEnvView && <MythicDialog fullWidth={true} maxWidth="lg" open={openEnvView}
|
||||
onClose={() => {
|
||||
setOpenEnvView(false);
|
||||
}}
|
||||
@@ -398,7 +394,7 @@ export function EventGroupTable({selectedEventGroup, me}) {
|
||||
}}/>}
|
||||
/>
|
||||
}
|
||||
{openTriggerDataView && <MythicDialog fullWidth={true} maxWidth="md" open={openTriggerDataView}
|
||||
{openTriggerDataView && <MythicDialog fullWidth={true} maxWidth="lg" open={openTriggerDataView}
|
||||
onClose={() => {
|
||||
setOpenTriggerDataView(false);
|
||||
}}
|
||||
@@ -412,7 +408,7 @@ export function EventGroupTable({selectedEventGroup, me}) {
|
||||
/>
|
||||
}
|
||||
{openFileManageView &&
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openFileManageView}
|
||||
<MythicDialog fullWidth={true} maxWidth="lg" open={openFileManageView}
|
||||
onClose={() => {
|
||||
setOpenFileManageView(false);
|
||||
}}
|
||||
@@ -432,7 +428,7 @@ export function EventGroupTable({selectedEventGroup, me}) {
|
||||
/>
|
||||
}
|
||||
{openConsumingContainerDialog &&
|
||||
<MythicDialog fullWidth={true} maxWidth="md" open={openConsumingContainerDialog}
|
||||
<MythicDialog fullWidth={true} maxWidth="lg" open={openConsumingContainerDialog}
|
||||
onClose={() => {
|
||||
setOpenConsumingContainerDialog(false);
|
||||
}}
|
||||
|
||||
@@ -1089,10 +1089,10 @@ function EventStepInstanceDetailDialog({selectedEventStepInstance, onClose}) {
|
||||
</div>
|
||||
|
||||
</DialogTitle>
|
||||
<DialogContent dividers={true} style={{height: "calc(75vh)"}}>
|
||||
<Typography style={{width: "100%", padding: "10px",
|
||||
<DialogContent dividers={true} style={{height: "calc(75vh)", padding: "5px"}}>
|
||||
<Typography style={{width: "100%", padding: "0px", paddingLeft: "10px",
|
||||
backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main}}
|
||||
variant={'h5'} component={Paper} >
|
||||
variant={'h6'} component={Paper} >
|
||||
Action: <b>{data.eventstepinstance_by_pk.eventstep.action}</b>
|
||||
</Typography>
|
||||
<Paper style={{marginTop: "5px"}}>
|
||||
@@ -1100,10 +1100,8 @@ function EventStepInstanceDetailDialog({selectedEventStepInstance, onClose}) {
|
||||
TransitionProps={{ unmountOnExit: true }} defaultExpanded={false}
|
||||
onChange={() => {setExpandStepTable(!expandStepTable)}} expanded={expandStepTable}
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls={`panel1c-stdout`}
|
||||
>Original and Instance Metadata
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls={`panel1c-stdout`}>
|
||||
Original and Instance Metadata
|
||||
</AccordionSummary>
|
||||
<AccordionDetails style={{cursor: "default"}}>
|
||||
<Table style={{width: "100%", overflow: "auto", tableLayout: "fixed", }}>
|
||||
@@ -1170,6 +1168,7 @@ function EventStepInstanceDetailDialog({selectedEventStepInstance, onClose}) {
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Paper>
|
||||
{ data.eventstepinstance_by_pk.stdout !== "" &&
|
||||
<Paper style={{marginTop: "5px"}}>
|
||||
<Accordion TransitionProps={{unmountOnExit: true}} defaultExpanded={false}
|
||||
onChange={() => {
|
||||
@@ -1201,6 +1200,8 @@ function EventStepInstanceDetailDialog({selectedEventStepInstance, onClose}) {
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Paper>
|
||||
}
|
||||
{data.eventstepinstance_by_pk.stderr !== "" &&
|
||||
<Paper style={{marginTop: "5px"}}>
|
||||
<Accordion TransitionProps={{ unmountOnExit: true }} defaultExpanded={false}
|
||||
onChange={() => {setExpandStderr(!expandStderr)}} expanded={expandStderr}
|
||||
@@ -1230,7 +1231,7 @@ function EventStepInstanceDetailDialog({selectedEventStepInstance, onClose}) {
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Paper>
|
||||
|
||||
}
|
||||
{data.eventstepinstance_by_pk.callbacks.length > 0 &&
|
||||
<>
|
||||
<Paper elevation={5} style={{backgroundColor: theme.pageHeader.main, color: theme.pageHeaderText.main,marginBottom: "5px", marginTop: "10px"}} variant={"elevation"}>
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
import React, {useContext} from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import {useMutation,useQuery, gql} from '@apollo/client';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import {IconButton} from '@mui/material';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Select from '@mui/material/Select';
|
||||
import MythicStyledTableCell from "../../MythicComponents/MythicTableCell";
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import {TextField} from '@mui/material';
|
||||
import {snackActions} from "../../utilities/Snackbar";
|
||||
import {MeContext} from "../../App";
|
||||
import {EventGroupTable} from "./EventGroupTable";
|
||||
|
||||
const triggerManualMutation = gql(`
|
||||
mutation triggerManualMutation($eventgroup_id: Int!, $env_data: jsonb){
|
||||
eventingTriggerManual(eventgroup_id: $eventgroup_id, env_data: $env_data){
|
||||
status
|
||||
error
|
||||
}
|
||||
}
|
||||
`);
|
||||
const triggerManualBulkMutation = gql(`
|
||||
mutation triggerManualBulkMutation($eventgroup_id: Int!, $env_data: jsonb, $trigger_context_type: String!, $trigger_context_ids: [Int!]!){
|
||||
eventingTriggerManualBulk(eventgroup_id: $eventgroup_id, env_data: $env_data, trigger_context_type: $trigger_context_type, trigger_context_ids: $trigger_context_ids){
|
||||
status
|
||||
error
|
||||
}
|
||||
}
|
||||
`);
|
||||
const getActiveWorkflows = gql(`
|
||||
query getActiveWorkflows {
|
||||
eventgroup(where: {deleted: {_eq: false}, active: {_eq: true}, approved_to_run: {_eq: true}}, order_by: {id: desc}){
|
||||
id
|
||||
operator {
|
||||
username
|
||||
}
|
||||
filemetum {
|
||||
agent_file_id
|
||||
id
|
||||
filename_text
|
||||
}
|
||||
filemeta(where: {deleted: {_eq: false}}) {
|
||||
agent_file_id
|
||||
id
|
||||
filename_text
|
||||
deleted
|
||||
}
|
||||
name
|
||||
description
|
||||
trigger
|
||||
trigger_data
|
||||
next_scheduled_run
|
||||
keywords
|
||||
environment
|
||||
active
|
||||
deleted
|
||||
created_at
|
||||
run_as
|
||||
approved_to_run
|
||||
eventgroupapprovals(order_by: {id: asc}) {
|
||||
id
|
||||
operator {
|
||||
id
|
||||
username
|
||||
}
|
||||
approved
|
||||
created_at
|
||||
updated_at
|
||||
}
|
||||
eventgroupconsumingcontainers {
|
||||
id
|
||||
consuming_container_name
|
||||
all_functions_available
|
||||
function_names
|
||||
consuming_container {
|
||||
container_running
|
||||
subscriptions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
export function EventTriggerContextSelectDialog({onClose, triggerContext}) {
|
||||
const me = useContext(MeContext);
|
||||
const [workflowOptions, setWorkflowOptions] = React.useState([]);
|
||||
const [selectedEventGroup, setSelectedEventGroup] = React.useState({id: 0});
|
||||
useQuery(getActiveWorkflows, {
|
||||
fetchPolicy: "no-cache",
|
||||
onCompleted: (data) => {
|
||||
setWorkflowOptions(data.eventgroup);
|
||||
if(data.eventgroup.length > 0){
|
||||
setSelectedEventGroup(data.eventgroup[0]);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(error);
|
||||
}
|
||||
})
|
||||
const [dictionaryData, setDictionaryData] = React.useState([]);
|
||||
const typeOptions = ["text", "number"];
|
||||
const [triggerManual] = useMutation(triggerManualMutation, {
|
||||
onCompleted: (data) => {
|
||||
if(data.eventingTriggerManual.status === "success"){
|
||||
snackActions.success("Successfully initiated trigger");
|
||||
onClose();
|
||||
} else {
|
||||
snackActions.error(data.eventingTriggerManual.error);
|
||||
}
|
||||
},
|
||||
onError: (data) => {
|
||||
console.log(data);
|
||||
}
|
||||
})
|
||||
const [triggerManualBulk] = useMutation(triggerManualBulkMutation, {
|
||||
onCompleted: (data) => {
|
||||
if(data.eventingTriggerManualBulk.status === "success"){
|
||||
snackActions.success("Successfully initiated trigger");
|
||||
onClose();
|
||||
} else {
|
||||
snackActions.error(data.eventingTriggerManualBulk.error);
|
||||
}
|
||||
},
|
||||
onError: (data) => {
|
||||
console.log(data);
|
||||
}
|
||||
})
|
||||
const handleWorkflowChange = (event) => {
|
||||
setSelectedEventGroup(event.target.value);
|
||||
}
|
||||
const addRow = () => {
|
||||
setDictionaryData([...dictionaryData, {
|
||||
"type": "text",
|
||||
"key": "",
|
||||
"value": "",
|
||||
}])
|
||||
}
|
||||
const changeRowType = (event, index) => {
|
||||
let newType = event.target.value;
|
||||
const newDictionaryData = dictionaryData.map( (row, i) => {
|
||||
if(index === i){
|
||||
return {key: row["key"], type: newType, value: newType === "number" ? 0 : ""};
|
||||
}
|
||||
return {...row};
|
||||
});
|
||||
setDictionaryData(newDictionaryData);
|
||||
}
|
||||
const removeRow = (index) => {
|
||||
let newDictionaryData = [...dictionaryData];
|
||||
newDictionaryData.splice(index, 1);
|
||||
setDictionaryData(newDictionaryData);
|
||||
}
|
||||
const onChangeRowKey = (value, index) => {
|
||||
const newDictionaryData = dictionaryData.map( (e, i) => {
|
||||
if(index === i){
|
||||
return {...e, key: value};
|
||||
}
|
||||
return {...e};
|
||||
});
|
||||
setDictionaryData(newDictionaryData);
|
||||
}
|
||||
const onChangeRowValue = (value, index) => {
|
||||
const newDictionaryData = dictionaryData.map( (e, i) => {
|
||||
if(index === i){
|
||||
return {...e, value: value};
|
||||
}
|
||||
return {...e};
|
||||
});
|
||||
setDictionaryData(newDictionaryData);
|
||||
}
|
||||
const onSubmit = () => {
|
||||
let keywordEnvData = dictionaryData.reduce( (prev, cur) => {
|
||||
try{
|
||||
if(cur.type === "number"){
|
||||
prev[cur.key] = parseInt(cur.value);
|
||||
} else {
|
||||
prev[cur.key] = cur.value;
|
||||
}
|
||||
}catch(error){
|
||||
prev[cur.key] = cur.value;
|
||||
}
|
||||
return {...prev};
|
||||
}, {});
|
||||
if(triggerContext.name !== undefined){
|
||||
keywordEnvData[triggerContext.name] = triggerContext.value;
|
||||
triggerManual({variables: {eventgroup_id: selectedEventGroup.id, env_data:keywordEnvData}});
|
||||
} else if(triggerContext.trigger_context_type !== undefined){
|
||||
triggerManualBulk({variables: {eventgroup_id: selectedEventGroup.id, env_data: keywordEnvData,
|
||||
trigger_context_type: triggerContext.trigger_context_type,
|
||||
trigger_context_ids: triggerContext.trigger_context_ids}})
|
||||
} else {
|
||||
snackActions.error("No trigger context name or type, can't submit");
|
||||
}
|
||||
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">Trigger a workflow</DialogTitle>
|
||||
<DialogContent dividers={true} style={{maxHeight: "calc(70vh)"}}>
|
||||
<DialogContentText>
|
||||
Trigger a workflow with selected context and optional additional data.
|
||||
</DialogContentText>
|
||||
<Select
|
||||
style={{marginBottom: "10px", width: "100%"}}
|
||||
value={selectedEventGroup}
|
||||
onChange={handleWorkflowChange}
|
||||
>
|
||||
{
|
||||
workflowOptions.map((opt, i) => (
|
||||
<MenuItem key={opt.id} value={opt}>{opt.name + " - " + opt.description}</MenuItem>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
{selectedEventGroup.id !== 0 &&
|
||||
<EventGroupTable me={me} selectedEventGroup={selectedEventGroup} showInstances={false} showGraph={false}/>
|
||||
}
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{width: "50%"}}>Key</TableCell>
|
||||
<TableCell>Value</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{dictionaryData.map( (e, index) => (
|
||||
<TableRow key={"dictionarydata" + index}>
|
||||
<MythicStyledTableCell style={{display: "flex"}} >
|
||||
<IconButton color={"error"} onClick={() => removeRow(index)} >
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<Select
|
||||
style={{}}
|
||||
value={e.type}
|
||||
onChange={(evt) => changeRowType(evt, index)}
|
||||
>
|
||||
{
|
||||
typeOptions.map((opt, i) => (
|
||||
<MenuItem key={"type" + opt} value={opt}>{opt}</MenuItem>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
<TextField value={e.key}
|
||||
style={{width: "100%"}}
|
||||
onChange={(evt) => onChangeRowKey(evt.target.value, index)} />
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<TextField value={e.value}
|
||||
style={{width: "100%"}}
|
||||
onChange={(evt) => onChangeRowValue(evt.target.value, index)} />
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
<TableRow>
|
||||
<MythicStyledTableCell>
|
||||
<Button onClick={addRow} color={"success"}>
|
||||
Add Entry
|
||||
</Button>
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell></MythicStyledTableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="contained" color="primary">
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={onSubmit} variant="contained" color="success">
|
||||
Submit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -126,6 +126,8 @@ export function EventTriggerKeywordDialog({onClose, selectedEventGroup}) {
|
||||
<TableCell style={{width: "50%"}}>Key</TableCell>
|
||||
<TableCell>Value</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{dictionaryData.map( (e, index) => (
|
||||
<TableRow key={"dictionarydata" + index}>
|
||||
<MythicStyledTableCell style={{display: "flex"}} >
|
||||
@@ -154,8 +156,6 @@ export function EventTriggerKeywordDialog({onClose, selectedEventGroup}) {
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<MythicStyledTableCell>
|
||||
<Button onClick={addRow} color={"success"}>
|
||||
|
||||
@@ -21,6 +21,8 @@ import {TestEventGroupFileDialog} from "./CreateEventWorkflowDialog";
|
||||
import AddCircleIcon from '@mui/icons-material/AddCircle';
|
||||
import Split from 'react-split';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import {CreateEventingStepper} from "./CreateEventingStepper";
|
||||
import CategoryIcon from '@mui/icons-material/Category';
|
||||
|
||||
const get_eventgroups = gql`
|
||||
query GetEventGroups {
|
||||
@@ -165,6 +167,7 @@ steps:
|
||||
export function Eventing({me}){
|
||||
const theme = useTheme();
|
||||
const [openTestModal, setOpenTestModal] = React.useState(false);
|
||||
const [openCreateEventingStepper, setOpenCreateEventingStepper] = React.useState(false);
|
||||
const [eventgroups, setEventgroups] = React.useState([]);
|
||||
const [showDeleted, setShowDeleted] = React.useState(false);
|
||||
const [selectedEventGroup, setSelectedEventGroup] = React.useState({id: 0});
|
||||
@@ -233,6 +236,11 @@ export function Eventing({me}){
|
||||
}
|
||||
evt.target.value = null;
|
||||
}
|
||||
const onCloseStepper = (e, success) => {
|
||||
if(success === true){
|
||||
setOpenCreateEventingStepper(false);
|
||||
}
|
||||
}
|
||||
React.useEffect( () => {
|
||||
if( !foundQueryEvent.current ){
|
||||
let queryParams = new URLSearchParams(window.location.search);
|
||||
@@ -267,9 +275,18 @@ export function Eventing({me}){
|
||||
<Button size={"small"} color={"success"}
|
||||
style={{display: "inline-flex", marginRight: "10px", marginLeft: "10px", marginTop: "5px"}}
|
||||
onClick={()=>setOpenTestModal(true)}
|
||||
startIcon={<AddCircleIcon />}
|
||||
startIcon={<AddCircleIcon style={{backgroundColor: "white", borderRadius: "10px"}} />}
|
||||
>
|
||||
Create New
|
||||
Text
|
||||
</Button>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Create Workflow with GUI Wizard"} tooltipStyle={{}} >
|
||||
<Button size={"small"} color={"success"}
|
||||
style={{display: "inline-flex", marginRight: "10px", marginLeft: "10px", marginTop: "5px"}}
|
||||
onClick={()=>setOpenCreateEventingStepper(true)}
|
||||
startIcon={<CategoryIcon />}
|
||||
>
|
||||
Wizard
|
||||
</Button>
|
||||
</MythicStyledTooltip>
|
||||
</Paper>
|
||||
@@ -286,6 +303,12 @@ export function Eventing({me}){
|
||||
}}/>}
|
||||
/>
|
||||
}
|
||||
{openCreateEventingStepper &&
|
||||
<MythicDialog fullWidth={true} maxWidth="xl" open={openCreateEventingStepper}
|
||||
onClose={() => setOpenCreateEventingStepper(false)}
|
||||
innerDialog={<CreateEventingStepper onClose={onCloseStepper} />}
|
||||
/>
|
||||
}
|
||||
<ListItem onClick={() => setSelectedEventGroup({id: 0})}
|
||||
style={selectedEventGroup.id === 0 ?
|
||||
{paddingTop: 0, paddingBottom: 0, borderLeft: `5px solid ${theme.palette.info.main}`} :
|
||||
@@ -330,7 +353,7 @@ export function Eventing({me}){
|
||||
</div>
|
||||
<div className="bg-gray-light" style={{display: "inline-flex"}}>
|
||||
<div style={{width: "100%", height: "100%"}}>
|
||||
<EventGroupTable selectedEventGroup={selectedEventGroup} me={me} />
|
||||
<EventGroupTable selectedEventGroup={selectedEventGroup} me={me} showInstances={true} showGraph={true} />
|
||||
</div>
|
||||
</div>
|
||||
</Split>
|
||||
|
||||
@@ -17,7 +17,7 @@ import { getDefaultValueForType } from '../CreatePayload/Step2SelectPayloadType'
|
||||
const GET_C2_Details = gql`
|
||||
query GetPC2Details($payload_name: String!) {
|
||||
c2profile(where: {name: {_eq: $payload_name}}) {
|
||||
c2profileparameters(where: {deleted: {_eq: false}}) {
|
||||
c2profileparameters(where: {deleted: {_eq: false}}, order_by: {name: asc}) {
|
||||
default_value
|
||||
description
|
||||
format_string
|
||||
|
||||
@@ -33,6 +33,7 @@ import AttachFileIcon from '@mui/icons-material/AttachFile';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import MythicTableCell from "../../MythicComponents/MythicTableCell";
|
||||
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
|
||||
import {MythicAgentSVGIcon} from "../../MythicComponents/MythicAgentSVGIcon";
|
||||
|
||||
|
||||
const toggleDeleteStatus = gql`
|
||||
@@ -162,11 +163,17 @@ export function C2ProfilesRow({service, showDeleted}) {
|
||||
)}
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{service.is_p2p ?
|
||||
(<FontAwesomeIcon icon={faLink} style={{width: "80px", height: "80px", padding: "10px"}} />)
|
||||
:
|
||||
(<WifiIcon style={{width: "80px", height: "80px", padding: "10px"}}/>)
|
||||
}
|
||||
{service.has_logo ? (
|
||||
<>
|
||||
<MythicAgentSVGIcon payload_type={service.name} is_p2p={service.is_p2p} style={{width: "80px", padding: "5px", objectFit: "unset"}} />
|
||||
</>
|
||||
) : (
|
||||
service.is_p2p ?
|
||||
(<FontAwesomeIcon icon={faLink} style={{width: "80px", height: "80px", padding: "10px"}} />)
|
||||
:
|
||||
(<WifiIcon style={{width: "80px", height: "80px", padding: "10px"}}/>)
|
||||
)}
|
||||
|
||||
</MythicTableCell>
|
||||
<MythicTableCell>
|
||||
{service.name}
|
||||
@@ -176,6 +183,11 @@ export function C2ProfilesRow({service, showDeleted}) {
|
||||
<Typography variant="body1" component="p">
|
||||
<b>Author:</b> {service.author}
|
||||
</Typography>
|
||||
{service.semver !== "" &&
|
||||
<Typography variant="body1" component="p">
|
||||
<b>Version:</b> {service.semver}
|
||||
</Typography>
|
||||
}
|
||||
<Typography variant="body1" component="p">
|
||||
<b>Supported Agents:</b> {service.payloadtypec2profiles.filter( (pt) => !pt.payloadtype.deleted ).map(c => c.payloadtype.name).join(", ")}
|
||||
</Typography>
|
||||
@@ -264,7 +276,6 @@ export function C2ProfilesRow({service, showDeleted}) {
|
||||
<br/>
|
||||
<MythicStyledTooltip title={"Documentation"}>
|
||||
<IconButton
|
||||
color={"secondary"}
|
||||
href={"/docs/c2-profiles/" + service.name.toLowerCase()}
|
||||
target="_blank"
|
||||
size="medium">
|
||||
@@ -273,7 +284,6 @@ export function C2ProfilesRow({service, showDeleted}) {
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={"Build Parameters"}>
|
||||
<IconButton
|
||||
color={"secondary"}
|
||||
onClick={()=>{setOpenBuildingDialog(true);}}
|
||||
size="medium">
|
||||
<BuildIcon />
|
||||
@@ -282,14 +292,12 @@ export function C2ProfilesRow({service, showDeleted}) {
|
||||
<MythicStyledTooltip title={"Save/Edit Instances for Building"}>
|
||||
<IconButton
|
||||
onClick={() => {setOpenProfileSavedInstancesDialog(true);}}
|
||||
color={"secondary"}
|
||||
size="medium">
|
||||
<SaveIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={service.container_running ? "View Files" : "Unable to view files because container is offline"}>
|
||||
<IconButton
|
||||
color={"secondary"}
|
||||
disabled={!service.container_running}
|
||||
onClick={()=>{setOpenListFilesDialog(true);}}
|
||||
size="medium">
|
||||
|
||||
+3
-6
@@ -22,7 +22,7 @@ import PermIdentityTwoToneIcon from '@mui/icons-material/PermIdentityTwoTone';
|
||||
import {MythicDialog} from "../../MythicComponents/MythicDialog";
|
||||
import AttachFileIcon from '@mui/icons-material/AttachFile';
|
||||
import {ConsumingServicesGetIDPMetadataDialog} from "./ConsumingServicesGetIDPMetadataDialog";
|
||||
import {C2ProfileListFilesDialog} from "../PayloadTypesC2Profiles/C2ProfileListFilesDialog";
|
||||
import {C2ProfileListFilesDialog} from "./C2ProfileListFilesDialog";
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
|
||||
@@ -207,7 +207,6 @@ export const ConsumingServicesTableRow = ({service, showDeleted}) => {
|
||||
<MythicTableCell>
|
||||
<MythicStyledTooltip title={w.container_running ? "View Files" : "Unable to view files since container is offline"}>
|
||||
<IconButton
|
||||
color={"secondary"}
|
||||
disabled={!w.container_running}
|
||||
onClick={()=>{onOpenListFilesDialog(w.name);}}
|
||||
size="medium">
|
||||
@@ -276,7 +275,6 @@ export const ConsumingServicesTableRow = ({service, showDeleted}) => {
|
||||
<MythicTableCell>
|
||||
<MythicStyledTooltip title={w.container_running ? "View Files" : "Unable to view files since container is offline"}>
|
||||
<IconButton
|
||||
color={"secondary"}
|
||||
disabled={!w.container_running}
|
||||
onClick={()=>{onOpenListFilesDialog(w.name);}}
|
||||
size="medium">
|
||||
@@ -345,7 +343,6 @@ export const ConsumingServicesTableRow = ({service, showDeleted}) => {
|
||||
<MythicTableCell>
|
||||
<MythicStyledTooltip title={w.container_running ? "View Files" : "Unable to view files since container is offline"}>
|
||||
<IconButton
|
||||
color={"secondary"}
|
||||
disabled={!w.container_running}
|
||||
onClick={()=>{onOpenListFilesDialog(w.name);}}
|
||||
size="medium">
|
||||
@@ -419,7 +416,6 @@ export const ConsumingServicesTableRow = ({service, showDeleted}) => {
|
||||
<MythicTableCell>
|
||||
<MythicStyledTooltip title={w.container_running ? "View Files" : "Unable to view files since container is offline"}>
|
||||
<IconButton
|
||||
color={"secondary"}
|
||||
disabled={!w.container_running}
|
||||
onClick={()=>{onOpenListFilesDialog(w.name);}}
|
||||
size="medium">
|
||||
@@ -431,7 +427,8 @@ export const ConsumingServicesTableRow = ({service, showDeleted}) => {
|
||||
{w.subscriptions.map(s => (
|
||||
<Typography key={s.name + s.type + w.name} style={{display: "block"}}>
|
||||
<MythicStyledTooltip title={"Fetch Container Metadata"} >
|
||||
<IconButton onClick={() => getIDPMetadata(w.name, s.name)} >
|
||||
<IconButton onClick={() => getIDPMetadata(w.name, s.name)}
|
||||
disabled={!w.container_running}>
|
||||
<PermIdentityTwoToneIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
@@ -17,7 +17,7 @@ import { getDefaultValueForType } from '../CreatePayload/Step2SelectPayloadType'
|
||||
const GET_Payload_Details = gql`
|
||||
query GetPayloadDetails($payload_name: String!) {
|
||||
payloadtype(where: {name: {_eq: $payload_name}}) {
|
||||
buildparameters (where: {deleted: {_eq: false} } ){
|
||||
buildparameters (where: {deleted: {_eq: false}}, order_by: {name: asc} ){
|
||||
description
|
||||
name
|
||||
id
|
||||
|
||||
@@ -97,6 +97,11 @@ export function PayloadTypeRow({service, showDeleted}){
|
||||
<Typography variant="body1" component="p">
|
||||
<b>Supported Operating Systems:</b> {supportedOS}
|
||||
</Typography>
|
||||
{service.semver !== "" &&
|
||||
<Typography variant="body1" component="p">
|
||||
<b>Version:</b> {service.semver}
|
||||
</Typography>
|
||||
}
|
||||
{service.wrap_these_payload_types.length === 0 ? null : (
|
||||
<Typography variant="body1" component="p">
|
||||
<b>Wrapped Payload Types:</b> {wrappedPayloads}
|
||||
@@ -114,7 +119,6 @@ export function PayloadTypeRow({service, showDeleted}){
|
||||
<MythicTableCell>
|
||||
<MythicStyledTooltip title={"Documentation"}>
|
||||
<IconButton
|
||||
color="secondary"
|
||||
href={service.wrapper ? "/docs/wrappers/" + service.name : "/docs/agents/" + service.name}
|
||||
target="_blank"
|
||||
size="medium">
|
||||
@@ -124,7 +128,6 @@ export function PayloadTypeRow({service, showDeleted}){
|
||||
<MythicStyledTooltip title={"Build Parameters"}>
|
||||
<IconButton
|
||||
onClick={()=>{setOpenBuildingDialog(true);}}
|
||||
color="secondary"
|
||||
size="medium">
|
||||
<BuildIcon />
|
||||
</IconButton>
|
||||
@@ -132,14 +135,12 @@ export function PayloadTypeRow({service, showDeleted}){
|
||||
<MythicStyledTooltip title={"Commands"}>
|
||||
<IconButton
|
||||
onClick={()=>{setOpenCommandsDialog(true)}}
|
||||
color={"secondary"}
|
||||
size={"medium"}>
|
||||
<SendTwoToneIcon />
|
||||
</IconButton>
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={service.container_running ? "View Files" : "Unable to view files because container is offline"}>
|
||||
<IconButton
|
||||
color={"secondary"}
|
||||
disabled={!service.container_running}
|
||||
onClick={()=>{setOpenListFilesDialog(true);}}
|
||||
size="medium">
|
||||
|
||||
@@ -79,7 +79,6 @@ export function PayloadTypeCommandDialog({service, payload_name, onClose}) {
|
||||
<TableCell>{param.needs_admin ? "True" : "False"}</TableCell>
|
||||
<TableCell>
|
||||
<IconButton
|
||||
color="secondary"
|
||||
href={service.wrapper ? "/docs/wrappers/" + service.name : "/docs/agents/" + service.name}
|
||||
target="_blank"
|
||||
size="large">
|
||||
|
||||
@@ -12,7 +12,7 @@ import {TranslationContainerRow} from "./TranslationContainerCard";
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Tabs from '@mui/material/Tabs';
|
||||
import {MythicSearchTabLabel, MythicTabPanel} from "../../MythicComponents/MythicTabPanel";
|
||||
import {ConsumingServicesTableRow} from '../ConsumingServices/ConsumingServicesTable';
|
||||
import {ConsumingServicesTableRow} from './ConsumingServicesTable';
|
||||
import {MythicStyledTooltip} from "../../MythicComponents/MythicStyledTooltip";
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Typography from '@mui/material/Typography';
|
||||
@@ -33,6 +33,7 @@ const SUB_Payload_Types = gql`
|
||||
supported_os
|
||||
wrapper
|
||||
agent_type
|
||||
semver
|
||||
translationcontainer {
|
||||
id
|
||||
name
|
||||
@@ -58,6 +59,8 @@ subscription getPayloadTypesSubscription {
|
||||
name
|
||||
running
|
||||
deleted
|
||||
semver
|
||||
has_logo
|
||||
payloadtypec2profiles(order_by: {payloadtype: {name: asc}}) {
|
||||
payloadtype {
|
||||
name
|
||||
|
||||
@@ -102,7 +102,6 @@ export function TranslationContainerRow({service, showDeleted}) {
|
||||
<MythicTableCell>
|
||||
<MythicStyledTooltip title={"Documentation"}>
|
||||
<IconButton
|
||||
color={"secondary"}
|
||||
href={"/docs/c2-profiles/" + service.name.toLowerCase()}
|
||||
target="_blank"
|
||||
size="medium">
|
||||
@@ -111,7 +110,6 @@ export function TranslationContainerRow({service, showDeleted}) {
|
||||
</MythicStyledTooltip>
|
||||
<MythicStyledTooltip title={service.container_running ? "View Files" : "Unable to view files because container is offline"}>
|
||||
<IconButton
|
||||
color={"secondary"}
|
||||
disabled={!service.container_running}
|
||||
onClick={()=>{setOpenListFilesDialog(true);}}
|
||||
size="medium">
|
||||
|
||||
@@ -134,6 +134,7 @@ export function Payloads(props){
|
||||
"showAutogenerated": false,
|
||||
});
|
||||
const mountedRef = React.useRef(true);
|
||||
const [openBackdrop, setOpenBackdrop] = React.useState(true);
|
||||
useSubscription(SUB_Payloads, {
|
||||
variables: {now: fromNow},
|
||||
fetchPolicy: "no-cache",
|
||||
@@ -171,6 +172,7 @@ export function Payloads(props){
|
||||
tempPageData.totalCount = data.payload_aggregate.aggregate.count;
|
||||
setPageData(tempPageData);
|
||||
setPayloads(data.payload);
|
||||
setOpenBackdrop(false);
|
||||
},
|
||||
onError: (data) => {
|
||||
|
||||
@@ -183,6 +185,7 @@ export function Payloads(props){
|
||||
tempPageData.totalCount = data.payload_aggregate.aggregate.count;
|
||||
setPageData(tempPageData);
|
||||
setPayloads(data.payload);
|
||||
setOpenBackdrop(false);
|
||||
},
|
||||
onError: (data) => {
|
||||
|
||||
@@ -190,6 +193,7 @@ export function Payloads(props){
|
||||
})
|
||||
const onChangePage = (event, value) => {
|
||||
snackActions.info("Fetching page...");
|
||||
setOpenBackdrop(true);
|
||||
fetchNewPage({variables: {offset: (value - 1) * pageData.fetchLimit, limit: pageData.fetchLimit, showDeleted: pageData.showDeleted, showAutogenerated: pageData.showAutogenerated}})
|
||||
}
|
||||
const [deletePayload] = useMutation(payloadsDelete, {
|
||||
@@ -319,8 +323,10 @@ export function Payloads(props){
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
return (
|
||||
<div style={{display: "flex", height: "100%", flexDirection: "column"}}>
|
||||
<PayloadsTable pageData={pageData} me={props.me}
|
||||
<div style={{display: "flex", height: "100%", flexDirection: "column", position: "relative"}}>
|
||||
|
||||
<PayloadsTable pageData={pageData} me={props.me}
|
||||
openBackdrop={openBackdrop}
|
||||
onDeletePayload={onDeletePayload}
|
||||
onUpdateCallbackAlert={onUpdateCallbackAlert}
|
||||
payload={payloads}
|
||||
|
||||
@@ -20,11 +20,12 @@ import MenuList from '@mui/material/MenuList';
|
||||
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||
import {useNavigate} from 'react-router-dom';
|
||||
import Pagination from '@mui/material/Pagination';
|
||||
import { Backdrop, CircularProgress } from '@mui/material';
|
||||
|
||||
|
||||
export function PayloadsTable({payload, onDeletePayload, onUpdateCallbackAlert, onRestorePayload, me,
|
||||
pageData, onChangePage, onChangeShowDeleted, onChangeShowAutogenerated,
|
||||
onCallbacksAllowedChanged}){
|
||||
onCallbacksAllowedChanged, openBackdrop}){
|
||||
const theme = useTheme();
|
||||
const [showDeleted, setShowDeleted] = React.useState(false);
|
||||
const [showAutogenerated, setShowAutogenerated] = React.useState(false);
|
||||
@@ -118,8 +119,12 @@ export function PayloadsTable({payload, onDeletePayload, onUpdateCallbackAlert,
|
||||
/>
|
||||
}
|
||||
</Paper>
|
||||
<div style={{flexGrow: 1, overflowY: "auto", height: "100%"}}>
|
||||
|
||||
<div style={{flexGrow: 1, overflowY: "auto", height: "100%", position: "relative"}}>
|
||||
{openBackdrop &&
|
||||
<Backdrop open={openBackdrop} style={{zIndex: 2000, position: "absolute"}}>
|
||||
<CircularProgress color="inherit" disableShrink />
|
||||
</Backdrop>
|
||||
}
|
||||
<Table stickyHeader size="small" style={{ "maxWidth": "100%", "overflow": "auto", tableLayout: "fixed"}}>
|
||||
<TableHead >
|
||||
<TableRow>
|
||||
|
||||
@@ -238,14 +238,14 @@ export function FileMetaDownloadTable(props){
|
||||
return (
|
||||
<TableContainer className="mythicElement" style={{display: "flex", flexDirection: "column", height: "100%"}} >
|
||||
<span style={{width: "100%", display: "flex"}}>
|
||||
<Button size="small" onClick={onDownloadBulk} style={{float: "right", width: "100%"}}
|
||||
color="primary" variant="contained"
|
||||
<Button size="small" onClick={onDownloadBulk} style={{float: "right", width: "100%", marginLeft: "10px", marginRight: "10px"}}
|
||||
color="primary" variant={disabled ? 'outlined': "contained"}
|
||||
disabled={disabled}
|
||||
>
|
||||
<ArchiveIcon/>Zip & Download Selected
|
||||
</Button>
|
||||
<Button size="small" onClick={onDeleteBulk} style={{width: "100%"}}
|
||||
color="error" variant="contained"
|
||||
<Button size="small" onClick={onDeleteBulk} style={{width: "100%", marginLeft: "10px", marginRight: "10px"}}
|
||||
color="error" variant={disabled ? 'outlined': "contained"}
|
||||
disabled={disabled}
|
||||
>
|
||||
<DeleteIcon />Delete Selected
|
||||
@@ -617,14 +617,14 @@ export function FileMetaUploadTable(props){
|
||||
return (
|
||||
<TableContainer className="mythicElement" style={{display: "flex", flexDirection: "column", height: "100%"}} >
|
||||
<span style={{width: "100%", display: "flex"}}>
|
||||
<Button size="small" onClick={onDownloadBulk} style={{float: "right", width: "100%"}}
|
||||
color="primary" variant="contained"
|
||||
<Button size="small" onClick={onDownloadBulk} style={{float: "right", width: "100%", marginLeft: "10px", marginRight: "10px"}}
|
||||
color="primary" variant={disabled ? 'outlined': "contained"}
|
||||
disabled={disabled}
|
||||
>
|
||||
<ArchiveIcon/>Zip & Download Selected
|
||||
</Button>
|
||||
<Button size="small" onClick={onDeleteBulk} style={{width: "100%"}}
|
||||
color="error" variant="contained"
|
||||
<Button size="small" onClick={onDeleteBulk} style={{width: "100%", marginLeft: "10px", marginRight: "10px"}}
|
||||
color="error" variant={disabled ? 'outlined': "contained"}
|
||||
disabled={disabled}
|
||||
>
|
||||
<DeleteIcon />Delete Selected
|
||||
@@ -1195,14 +1195,14 @@ export function FileMetaEventingWorkflowsTable(props){
|
||||
return (
|
||||
<TableContainer className="mythicElement" style={{display: "flex", flexDirection: "column", height: "100%"}} >
|
||||
<span style={{width: "100%", display: "flex"}}>
|
||||
<Button size="small" onClick={onDownloadBulk} style={{float: "right", width: "100%"}}
|
||||
color="primary" variant="contained"
|
||||
<Button size="small" onClick={onDownloadBulk} style={{float: "right", width: "100%", marginLeft: "10px", marginRight: "10px"}}
|
||||
color="primary" variant={disabled ? 'outlined': "contained"}
|
||||
disabled={disabled}
|
||||
>
|
||||
<ArchiveIcon/>Zip & Download Selected
|
||||
</Button>
|
||||
<Button size="small" onClick={onDeleteBulk} style={{width: "100%"}}
|
||||
color="error" variant="contained"
|
||||
<Button size="small" onClick={onDeleteBulk} style={{width: "100%", marginLeft: "10px", marginRight: "10px"}}
|
||||
color="error" variant={disabled ? 'outlined': "contained"}
|
||||
disabled={disabled}
|
||||
>
|
||||
<DeleteIcon />Delete Selected
|
||||
|
||||
@@ -46,6 +46,7 @@ mutation createAPITokenMutation($operator_id: Int, $name: String){
|
||||
operator_id
|
||||
name
|
||||
created_by
|
||||
creation_time
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -70,9 +71,10 @@ mutation toggleAPITokenActiveMutation($id: Int!, $active: Boolean!){
|
||||
`;
|
||||
const GetAPITokens = gql`
|
||||
query getUserAPITokens($operator_id: Int!){
|
||||
apitokens(where: {operator_id: {_eq: $operator_id}}) {
|
||||
apitokens(where: {operator_id: {_eq: $operator_id}}, order_by: {id: desc}) {
|
||||
token_value
|
||||
token_type
|
||||
creation_time
|
||||
active
|
||||
name
|
||||
deleted
|
||||
@@ -227,7 +229,7 @@ export function SettingsOperatorTableRow(props){
|
||||
<MythicDialog open={openDelete}
|
||||
onClose={()=>{setOpenDeleteDialog(false);}}
|
||||
innerDialog={<SettingsOperatorDeleteDialog onClose={()=>{setOpenDeleteDialog(false);}} onAccept={onAcceptDelete} {...props} />}
|
||||
/>
|
||||
/>
|
||||
}
|
||||
|
||||
</MythicStyledTableCell>
|
||||
@@ -353,44 +355,43 @@ export function SettingsOperatorTableRow(props){
|
||||
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
{((props.id === me.user.id) || (props.userIsAdmin && props.account_type === "bot")) &&
|
||||
<MythicStyledTableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={10}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6" gutterBottom component="div" style={{display: "inline-block"}}>
|
||||
API Tokens
|
||||
</Typography>
|
||||
{showDeleted ? (
|
||||
<MythicStyledTooltip title={"Hide API Tokens"} tooltipStyle={{float: "right"}}>
|
||||
<IconButton size="small" style={{float: "right"}} variant="contained" onClick={() => setShowDeleted(!showDeleted)}><VisibilityIcon /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
{((props.id === me.user.id) || (props.userIsAdmin && props.account_type === "bot")) && open &&
|
||||
<TableRow>
|
||||
<MythicStyledTableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={10}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6" gutterBottom component="div" style={{display: "inline-block"}}>
|
||||
API Tokens
|
||||
</Typography>
|
||||
{showDeleted ? (
|
||||
<MythicStyledTooltip title={"Hide API Tokens"} tooltipStyle={{float: "right"}}>
|
||||
<IconButton size="small" style={{float: "right"}} variant="contained" onClick={() => setShowDeleted(!showDeleted)}><VisibilityIcon /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
|
||||
) : (
|
||||
<MythicStyledTooltip title={"Show Deleted API Tokens"} tooltipStyle={{float: "right"}}>
|
||||
<IconButton size="small" style={{float: "right"}} variant="contained" onClick={() => setShowDeleted(!showDeleted)} ><VisibilityOffIcon /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)}
|
||||
) : (
|
||||
<MythicStyledTooltip title={"Show Deleted API Tokens"} tooltipStyle={{float: "right"}}>
|
||||
<IconButton size="small" style={{float: "right"}} variant="contained" onClick={() => setShowDeleted(!showDeleted)} ><VisibilityOffIcon /></IconButton>
|
||||
</MythicStyledTooltip>
|
||||
)}
|
||||
|
||||
<Button size="small" onClick={() => {setOpenNewAPIToken(true)}} style={{marginRight: "20px", float: "right"}}
|
||||
variant={"contained"}
|
||||
startIcon={<AddCircleOutlineOutlinedIcon color="success" />} >
|
||||
API Token
|
||||
</Button>
|
||||
{openNewAPIToken &&
|
||||
<MythicDialog open={openNewAPIToken}
|
||||
fullWidth={true}
|
||||
onClose={()=>{setOpenNewAPIToken(false);}}
|
||||
innerDialog={<SettingsAPITokenDialog title="New API Token Name" onAccept={onSubmitNewAPIToken} handleClose={()=>{setOpenNewAPIToken(false);}} {...props}/>}
|
||||
/>
|
||||
}
|
||||
<APITokens apiTokens={apiTokens} onDeleteAPIToken={onDeleteAPIToken} onToggleActive={onToggleActive} showDeleted={showDeleted} />
|
||||
</Box>
|
||||
</Collapse>
|
||||
</MythicStyledTableCell>
|
||||
<Button size="small" onClick={() => {setOpenNewAPIToken(true)}} style={{marginRight: "20px", float: "right"}}
|
||||
variant={"contained"}
|
||||
startIcon={<AddCircleOutlineOutlinedIcon color="success" />} >
|
||||
API Token
|
||||
</Button>
|
||||
{openNewAPIToken &&
|
||||
<MythicDialog open={openNewAPIToken}
|
||||
fullWidth={true}
|
||||
onClose={()=>{setOpenNewAPIToken(false);}}
|
||||
innerDialog={<SettingsAPITokenDialog title="New API Token Name" onAccept={onSubmitNewAPIToken} handleClose={()=>{setOpenNewAPIToken(false);}} {...props}/>}
|
||||
/>
|
||||
}
|
||||
<APITokens me={me} apiTokens={apiTokens} onDeleteAPIToken={onDeleteAPIToken} onToggleActive={onToggleActive} showDeleted={showDeleted} />
|
||||
</Box>
|
||||
</Collapse>
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
}
|
||||
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
@@ -429,6 +430,16 @@ const columns = [
|
||||
headerName: 'Created By',
|
||||
flex: 1,
|
||||
valueGetter: (value, row) => row.created_by_operator?.username,
|
||||
renderCell: (params) => (
|
||||
<>
|
||||
<Typography>
|
||||
{params.row?.created_by_operator?.username}
|
||||
</Typography>
|
||||
<Typography style={{fontSize: params.row?.theme?.typography.pxToRem(12),}}>
|
||||
Created at: {toLocalTime(params.row?.creation_time, params.row.me?.user?.view_utc_time )}
|
||||
</Typography>
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
field: 'token',
|
||||
@@ -479,7 +490,7 @@ const columns = [
|
||||
}
|
||||
},
|
||||
];
|
||||
const APITokens = ({apiTokens, onDeleteAPIToken, onToggleActive, showDeleted}) => {
|
||||
const APITokens = ({apiTokens, onDeleteAPIToken, onToggleActive, showDeleted, me}) => {
|
||||
const onCopyTokenValue = (token_value) => {
|
||||
let success = copyStringToClipboard(token_value);
|
||||
if(success){
|
||||
@@ -488,12 +499,14 @@ const APITokens = ({apiTokens, onDeleteAPIToken, onToggleActive, showDeleted}) =
|
||||
snackActions.error("failed to copy token to clipboard");
|
||||
}
|
||||
}
|
||||
const theme = useTheme();
|
||||
const [data, setData] = React.useState([]);
|
||||
React.useEffect( () => {
|
||||
setData(apiTokens.reduce( (prev, c) => {
|
||||
if(showDeleted || (!showDeleted && !c.deleted)){
|
||||
return [...prev, {...c, onCopyTokenValue: onCopyTokenValue,
|
||||
onDeleteAPIToken: onDeleteAPIToken, onToggleActive: onToggleActive,
|
||||
me: me, theme: theme
|
||||
}];
|
||||
}
|
||||
return [...prev];
|
||||
|
||||
@@ -425,12 +425,7 @@ export function SettingsOperatorUIConfigDialog(props) {
|
||||
<TableContainer className="mythicElement" style={{paddingLeft: "10px", paddingRight: "10px"}}>
|
||||
<Table size="small" style={{ "maxWidth": "100%", "overflow": "scroll"}}>
|
||||
<TableBody>
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell style={{width: "30%"}}>Font Size</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<MythicTextField type="number" value={fontSize} onChange={onChangeFontSize} showLabel={false} />
|
||||
</MythicStyledTableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow hover>
|
||||
<MythicStyledTableCell>Font Family</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
|
||||
@@ -130,7 +130,7 @@ const stillDoNotDisturb = () => {
|
||||
}
|
||||
const ToastComponent = ({msg, closeButton}) => {
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "space-between", alignItems: "center"}}>
|
||||
<div style={{display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%"}}>
|
||||
{msg}
|
||||
{closeButton}
|
||||
</div>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#FROM itsafeaturemythic/mythic_go_base:latest
|
||||
FROM golang:1.21-alpine
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
ARG GOPROXY=proxy.golang.org
|
||||
ARG GO111MODULE
|
||||
|
||||
RUN go env -w GOPROXY=${GOPROXY}
|
||||
RUN go env -w GO111MODULE=${GO111MODULE}
|
||||
|
||||
COPY ["src/", "."]
|
||||
|
||||
RUN apk add --no-cache make
|
||||
|
||||
RUN make build_all
|
||||
|
||||
FROM alpine
|
||||
|
||||
COPY --from=0 /usr/src/app/mythic-cli_linux /mythic-cli_linux
|
||||
COPY --from=0 /usr/src/app/mythic-cli_macos /mythic-cli_macos
|
||||
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 0.3.18 - 2025-09-29
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated uninstall to also remove associated .env values
|
||||
|
||||
## 0.3.17 - 2025-07-16
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -356,7 +356,7 @@ Setting this to "true" means that the local Mythic/hasura-docker/Dockerfile is u
|
||||
|
||||
mythicEnv.SetDefault("COMPOSE_BAKE", true)
|
||||
mythicEnvInfo["COMPOSE_BAKE"] = `This is a newer option in Docker Compose to delegate builds to bake for better build performance`
|
||||
|
||||
|
||||
mythicEnv.SetDefault("REBUILD_ON_START", false)
|
||||
mythicEnvInfo["rebuild_on_start"] = `This identifies if a container's backing image should be re-built (or re-fetched) each time you start the container.
|
||||
This can cause agent and c2 profile containers to have their volumes wiped on each start (and thus deleting any changes).
|
||||
@@ -537,7 +537,11 @@ func writeMythicEnvironmentVariables() {
|
||||
if len(mythicEnv.GetString(key)) == 0 {
|
||||
_, err = f.WriteString(fmt.Sprintf("%s=\n", strings.ToUpper(key)))
|
||||
} else {
|
||||
_, err = f.WriteString(fmt.Sprintf("%s=\"%s\"\n", strings.ToUpper(key), mythicEnv.GetString(key)))
|
||||
value := mythicEnv.GetString(key)
|
||||
value = strings.ReplaceAll(value, "\\", "\\\\")
|
||||
value = strings.ReplaceAll(value, "'", "\\'")
|
||||
value = strings.ReplaceAll(value, "\"", "\\\"")
|
||||
_, err = f.WriteString(fmt.Sprintf("%s=\"%s\"\n", strings.ToUpper(key), value))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -575,6 +579,27 @@ func GetConfigStrings(args []string) map[string]string {
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
func RemoveConfigStrings(args []string) error {
|
||||
allSettings := mythicEnv.AllKeys()
|
||||
mythicStruct := mythicEnv.AllSettings()
|
||||
for i := 0; i < len(args[0:]); i++ {
|
||||
searchRegex, err := regexp.Compile(args[i])
|
||||
if err != nil {
|
||||
log.Fatalf("[!] bad regex: %v", err)
|
||||
}
|
||||
for _, setting := range allSettings {
|
||||
if searchRegex.MatchString(strings.ToUpper(setting)) || searchRegex.MatchString(strings.ToLower(setting)) {
|
||||
mythicStruct[setting] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
err := mythicEnv.MergeConfigMap(mythicStruct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeMythicEnvironmentVariables()
|
||||
return nil
|
||||
}
|
||||
func SetConfigStrings(key string, value string) {
|
||||
allSettings := mythicEnv.AllKeys()
|
||||
searchRegex, err := regexp.Compile(key)
|
||||
|
||||
@@ -4,5 +4,5 @@ package config
|
||||
|
||||
var (
|
||||
// Version Mythic CLI version
|
||||
Version = "v0.3.18"
|
||||
Version = "v0.3.19"
|
||||
)
|
||||
|
||||
@@ -488,7 +488,18 @@ func UninstallService(services []string) {
|
||||
}
|
||||
|
||||
if found {
|
||||
log.Printf("[+] Successfully Uninstalled %s\n", service)
|
||||
log.Printf("[*] Removing associated .ENV settings...\n", service)
|
||||
err := config.RemoveConfigStrings([]string{
|
||||
fmt.Sprintf("%s_remote_image", service),
|
||||
fmt.Sprintf("%s_use_volume", service),
|
||||
fmt.Sprintf("%s_use_build_context", service),
|
||||
fmt.Sprintf("%s_install_location", service),
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("[-] Failed to remove .ENV settings: %v\n", err)
|
||||
} else {
|
||||
log.Printf("[+] Successfully Uninstalled %s\n", service)
|
||||
}
|
||||
if manager.GetManager().IsServiceRunning("mythic_documentation") {
|
||||
log.Printf("[*] Restarting mythic_documentation container to pull in changes\n")
|
||||
ServiceStop([]string{"mythic_documentation"}, true)
|
||||
|
||||
@@ -27,7 +27,7 @@ func DatabaseReset(force bool) {
|
||||
}
|
||||
func RabbitmqReset(force bool, startAgain bool) {
|
||||
if force {
|
||||
manager.GetManager().StopServices([]string{"mythic_rabbitmq"}, config.GetMythicEnv().GetBool("REBUILD_ON_START"), false)
|
||||
manager.GetManager().RemoveContainers([]string{"mythic_rabbitmq"}, false)
|
||||
log.Printf("[*] Removing rabbitmq storage files\n")
|
||||
manager.GetManager().ResetRabbitmq(config.GetMythicEnv().GetBool("rabbitmq_use_volume"))
|
||||
if startAgain {
|
||||
@@ -37,7 +37,7 @@ func RabbitmqReset(force bool, startAgain bool) {
|
||||
}
|
||||
confirm := config.AskConfirm("Are you sure you want to reset the rabbitmq storage? ")
|
||||
if confirm {
|
||||
manager.GetManager().StopServices([]string{"mythic_rabbitmq"}, config.GetMythicEnv().GetBool("REBUILD_ON_START"), false)
|
||||
manager.GetManager().RemoveContainers([]string{"mythic_rabbitmq"}, false)
|
||||
log.Printf("[*] Removing rabbitmq storage files\n")
|
||||
manager.GetManager().ResetRabbitmq(config.GetMythicEnv().GetBool("rabbitmq_use_volume"))
|
||||
if startAgain {
|
||||
|
||||
@@ -936,11 +936,14 @@ func (d *DockerComposeManager) ResetRabbitmq(useVolume bool) {
|
||||
if !useVolume {
|
||||
workingPath := utils.GetCwdFromExe()
|
||||
err := os.RemoveAll(filepath.Join(workingPath, "rabbitmq-docker", "storage"))
|
||||
if err != nil {
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Fatalf("[-] Failed to remove rabbitmq storage files\n%v\n", err)
|
||||
} else {
|
||||
log.Printf("[+] Successfully reset rabbitmq storage files\n")
|
||||
}
|
||||
err = os.Remove(filepath.Join(workingPath, "rabbitmq-docker", "storage"))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Fatalf("[-] Failed to remove rabbitmq storage files\n%v\n", err)
|
||||
}
|
||||
log.Printf("[+] Successfully reset rabbitmq storage files\n")
|
||||
} else {
|
||||
_ = d.RemoveContainers([]string{"mythic_rabbitmq"}, false)
|
||||
err := d.RemoveVolume("mythic_rabbitmq_volume")
|
||||
|
||||
@@ -16,7 +16,6 @@ var rabbitmqResetCmd = &cobra.Command{
|
||||
var startAgain bool
|
||||
|
||||
func init() {
|
||||
rabbitmqCmd.AddCommand(rabbitmqResetCmd)
|
||||
rabbitmqResetCmd.Flags().BoolVarP(
|
||||
&force,
|
||||
"force",
|
||||
@@ -28,9 +27,10 @@ func init() {
|
||||
&startAgain,
|
||||
"start-again",
|
||||
"s",
|
||||
true,
|
||||
false,
|
||||
`Start the container again after making changes`,
|
||||
)
|
||||
rabbitmqCmd.AddCommand(rabbitmqResetCmd)
|
||||
}
|
||||
|
||||
func rabbitmqReset(cmd *cobra.Command, args []string) {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
FROM klakegg/hugo:latest
|
||||
|
||||
COPY [".", "/src/"]
|
||||
HEALTHCHECK --interval=30s --timeout=10s --retries=5 --start-period=10s \
|
||||
CMD wget -nv -t1 -O /dev/null http://127.0.0.1:${DOCUMENTATION_PORT:-8090}/docs/ || exit 1
|
||||
|
||||
RUN hugo new site mythic_docs
|
||||
|
||||
ENTRYPOINT hugo server -p ${DOCUMENTATION_PORT:-8090}
|
||||
@@ -1,3 +1,9 @@
|
||||
FROM klakegg/hugo:latest
|
||||
|
||||
FROM ghcr.io/its-a-feature/mythic_documentation:v3.3.0.136
|
||||
COPY [".", "/src/"]
|
||||
HEALTHCHECK --interval=30s --timeout=10s --retries=5 --start-period=10s \
|
||||
CMD wget -nv -t1 -O /dev/null http://127.0.0.1:${DOCUMENTATION_PORT:-8090}/docs/ || exit 1
|
||||
|
||||
RUN hugo new site mythic_docs
|
||||
|
||||
ENTRYPOINT hugo server -p ${DOCUMENTATION_PORT:-8090}
|
||||
@@ -1,4 +0,0 @@
|
||||
FROM hasura/graphql-engine:latest.cli-migrations-v2
|
||||
ENV HASURA_GRAPHQL_SERVER_PORT=8080
|
||||
HEALTHCHECK CMD curl -f http://localhost:${HASURA_GRAPHQL_SERVER_PORT}/healthz || exit 1
|
||||
COPY ["metadata/", "/metadata"]
|
||||
@@ -304,9 +304,19 @@ type Mutation {
|
||||
type Mutation {
|
||||
eventingTriggerManual(
|
||||
eventgroup_id: Int!
|
||||
env_data: jsonb
|
||||
): eventingTriggerManualOutput
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
eventingTriggerManualBulk(
|
||||
eventgroup_id: Int!
|
||||
env_data: jsonb
|
||||
trigger_context_type: String!
|
||||
trigger_context_ids: [Int!]!
|
||||
): eventingTriggerManualBulkOutput
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
eventingTriggerRetry(
|
||||
eventgroupinstance_id: Int!
|
||||
@@ -1236,3 +1246,8 @@ type updatePayloadOutput {
|
||||
payload_uuid: String
|
||||
}
|
||||
|
||||
type eventingTriggerManualBulkOutput {
|
||||
status: String!
|
||||
error: String
|
||||
}
|
||||
|
||||
|
||||
@@ -406,6 +406,16 @@ actions:
|
||||
- role: operation_admin
|
||||
- role: mythic_admin
|
||||
- role: developer
|
||||
- name: eventingTriggerManualBulk
|
||||
definition:
|
||||
kind: synchronous
|
||||
handler: '{{MYTHIC_ACTIONS_URL_BASE}}/eventing_trigger_manual_bulk_webhook'
|
||||
forward_client_headers: true
|
||||
permissions:
|
||||
- role: operator
|
||||
- role: operation_admin
|
||||
- role: mythic_admin
|
||||
- role: developer
|
||||
- name: eventingTriggerRetry
|
||||
definition:
|
||||
kind: synchronous
|
||||
@@ -934,4 +944,5 @@ custom_types:
|
||||
- name: dynamicQueryOutputChoice
|
||||
- name: createArtifactOutput
|
||||
- name: updatePayloadOutput
|
||||
- name: eventingTriggerManualBulkOutput
|
||||
scalars: []
|
||||
|
||||
@@ -1185,6 +1185,7 @@
|
||||
columns:
|
||||
- container_running
|
||||
- deleted
|
||||
- has_logo
|
||||
- is_p2p
|
||||
- is_server_routed
|
||||
- running
|
||||
@@ -1192,27 +1193,31 @@
|
||||
- author
|
||||
- description
|
||||
- name
|
||||
- semver
|
||||
- creation_time
|
||||
filter: {}
|
||||
- role: mythic_admin
|
||||
permission:
|
||||
columns:
|
||||
- id
|
||||
- name
|
||||
- description
|
||||
- creation_time
|
||||
- running
|
||||
- container_running
|
||||
- author
|
||||
- deleted
|
||||
- has_logo
|
||||
- is_p2p
|
||||
- is_server_routed
|
||||
- deleted
|
||||
- running
|
||||
- id
|
||||
- author
|
||||
- description
|
||||
- name
|
||||
- semver
|
||||
- creation_time
|
||||
filter: {}
|
||||
- role: operation_admin
|
||||
permission:
|
||||
columns:
|
||||
- container_running
|
||||
- deleted
|
||||
- has_logo
|
||||
- is_p2p
|
||||
- is_server_routed
|
||||
- running
|
||||
@@ -1220,6 +1225,7 @@
|
||||
- author
|
||||
- description
|
||||
- name
|
||||
- semver
|
||||
- creation_time
|
||||
filter: {}
|
||||
- role: operator
|
||||
@@ -1227,6 +1233,7 @@
|
||||
columns:
|
||||
- container_running
|
||||
- deleted
|
||||
- has_logo
|
||||
- is_p2p
|
||||
- is_server_routed
|
||||
- running
|
||||
@@ -1234,6 +1241,7 @@
|
||||
- author
|
||||
- description
|
||||
- name
|
||||
- semver
|
||||
- creation_time
|
||||
filter: {}
|
||||
- role: spectator
|
||||
@@ -1241,6 +1249,7 @@
|
||||
columns:
|
||||
- container_running
|
||||
- deleted
|
||||
- has_logo
|
||||
- is_p2p
|
||||
- is_server_routed
|
||||
- running
|
||||
@@ -1248,6 +1257,7 @@
|
||||
- author
|
||||
- description
|
||||
- name
|
||||
- semver
|
||||
- creation_time
|
||||
filter: {}
|
||||
update_permissions:
|
||||
@@ -2751,6 +2761,7 @@
|
||||
- subscriptions
|
||||
- description
|
||||
- name
|
||||
- semver
|
||||
- type
|
||||
- created_at
|
||||
- updated_at
|
||||
@@ -2765,6 +2776,7 @@
|
||||
- subscriptions
|
||||
- description
|
||||
- name
|
||||
- semver
|
||||
- type
|
||||
- created_at
|
||||
- updated_at
|
||||
@@ -2779,6 +2791,7 @@
|
||||
- subscriptions
|
||||
- description
|
||||
- name
|
||||
- semver
|
||||
- type
|
||||
- created_at
|
||||
- updated_at
|
||||
@@ -2793,6 +2806,7 @@
|
||||
- subscriptions
|
||||
- description
|
||||
- name
|
||||
- semver
|
||||
- type
|
||||
- created_at
|
||||
- updated_at
|
||||
@@ -2807,6 +2821,7 @@
|
||||
- subscriptions
|
||||
- description
|
||||
- name
|
||||
- semver
|
||||
- type
|
||||
- created_at
|
||||
- updated_at
|
||||
@@ -5383,6 +5398,7 @@
|
||||
- operator_id
|
||||
- resolved
|
||||
- source
|
||||
- warning
|
||||
comment: ""
|
||||
- role: mythic_admin
|
||||
permission:
|
||||
@@ -5399,6 +5415,7 @@
|
||||
- operator_id
|
||||
- resolved
|
||||
- source
|
||||
- warning
|
||||
comment: ""
|
||||
- role: operation_admin
|
||||
permission:
|
||||
@@ -5415,6 +5432,7 @@
|
||||
- operator_id
|
||||
- resolved
|
||||
- source
|
||||
- warning
|
||||
comment: ""
|
||||
- role: operator
|
||||
permission:
|
||||
@@ -5431,22 +5449,33 @@
|
||||
- operator_id
|
||||
- resolved
|
||||
- source
|
||||
- warning
|
||||
comment: ""
|
||||
- role: spectator
|
||||
permission:
|
||||
check: {}
|
||||
set:
|
||||
operation_id: x-hasura-current-operation-id
|
||||
operator_id: x-hasura-user-id
|
||||
columns:
|
||||
- message
|
||||
comment: ""
|
||||
select_permissions:
|
||||
- role: developer
|
||||
permission:
|
||||
columns:
|
||||
- deleted
|
||||
- resolved
|
||||
- apitokens_id
|
||||
- count
|
||||
- deleted
|
||||
- id
|
||||
- operation_id
|
||||
- operator_id
|
||||
- level
|
||||
- message
|
||||
- operation_id
|
||||
- operator_id
|
||||
- resolved
|
||||
- source
|
||||
- timestamp
|
||||
- warning
|
||||
filter:
|
||||
operation_id:
|
||||
_eq: X-Hasura-current-operation-id
|
||||
@@ -5456,6 +5485,7 @@
|
||||
columns:
|
||||
- deleted
|
||||
- resolved
|
||||
- warning
|
||||
- apitokens_id
|
||||
- count
|
||||
- id
|
||||
@@ -5474,6 +5504,7 @@
|
||||
columns:
|
||||
- deleted
|
||||
- resolved
|
||||
- warning
|
||||
- apitokens_id
|
||||
- count
|
||||
- id
|
||||
@@ -5492,6 +5523,7 @@
|
||||
columns:
|
||||
- deleted
|
||||
- resolved
|
||||
- warning
|
||||
- apitokens_id
|
||||
- count
|
||||
- id
|
||||
@@ -5510,6 +5542,7 @@
|
||||
columns:
|
||||
- deleted
|
||||
- resolved
|
||||
- warning
|
||||
- apitokens_id
|
||||
- count
|
||||
- id
|
||||
@@ -5530,6 +5563,7 @@
|
||||
- deleted
|
||||
- level
|
||||
- resolved
|
||||
- warning
|
||||
filter:
|
||||
operation_id:
|
||||
_in: x-hasura-operations
|
||||
@@ -5540,6 +5574,7 @@
|
||||
- deleted
|
||||
- level
|
||||
- resolved
|
||||
- warning
|
||||
filter:
|
||||
operation_id:
|
||||
_in: x-hasura-operations
|
||||
@@ -5550,6 +5585,7 @@
|
||||
- deleted
|
||||
- level
|
||||
- resolved
|
||||
- warning
|
||||
filter:
|
||||
operation_id:
|
||||
_in: x-hasura-operations
|
||||
@@ -5560,6 +5596,7 @@
|
||||
- deleted
|
||||
- level
|
||||
- resolved
|
||||
- warning
|
||||
filter:
|
||||
operation_id:
|
||||
_in: x-hasura-operations
|
||||
@@ -8998,52 +9035,57 @@
|
||||
- role: developer
|
||||
permission:
|
||||
columns:
|
||||
- author
|
||||
- container_running
|
||||
- deleted
|
||||
- description
|
||||
- id
|
||||
- name
|
||||
- deleted
|
||||
- container_running
|
||||
- description
|
||||
- author
|
||||
- semver
|
||||
filter: {}
|
||||
- role: mythic_admin
|
||||
permission:
|
||||
columns:
|
||||
- author
|
||||
- container_running
|
||||
- deleted
|
||||
- description
|
||||
- id
|
||||
- name
|
||||
- deleted
|
||||
- container_running
|
||||
- description
|
||||
- author
|
||||
- semver
|
||||
filter: {}
|
||||
- role: operation_admin
|
||||
permission:
|
||||
columns:
|
||||
- author
|
||||
- container_running
|
||||
- deleted
|
||||
- description
|
||||
- id
|
||||
- name
|
||||
- deleted
|
||||
- container_running
|
||||
- description
|
||||
- author
|
||||
- semver
|
||||
filter: {}
|
||||
- role: operator
|
||||
permission:
|
||||
columns:
|
||||
- author
|
||||
- container_running
|
||||
- deleted
|
||||
- description
|
||||
- id
|
||||
- name
|
||||
- deleted
|
||||
- container_running
|
||||
- description
|
||||
- author
|
||||
- semver
|
||||
filter: {}
|
||||
- role: spectator
|
||||
permission:
|
||||
columns:
|
||||
- author
|
||||
- container_running
|
||||
- deleted
|
||||
- description
|
||||
- id
|
||||
- name
|
||||
- deleted
|
||||
- container_running
|
||||
- description
|
||||
- author
|
||||
- semver
|
||||
filter: {}
|
||||
update_permissions:
|
||||
- role: developer
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
FROM jupyter/minimal-notebook:python-3.11
|
||||
|
||||
# for offline builds, add ability for custom PIP repositories
|
||||
ARG PIP_INDEX
|
||||
ARG PIP_INDEX_URL
|
||||
ARG PIP_TRUSTED_HOST
|
||||
|
||||
RUN pip3 install mythic==0.2.8
|
||||
|
||||
WORKDIR /projects
|
||||
|
||||
#CMD start.sh jupyter lab --ServerApp.open_browser=false --IdentityProvider.token='' --ServerApp.base_url="/jupyter" --ServerApp.default_url="/jupyter"
|
||||
|
||||
ENV JUPYTERHUB_SERVICE_PREFIX "/jupyter/"
|
||||
ENV JUPYTER_TOKEN "mythic"
|
||||
|
||||
COPY ["jupyter/", "."]
|
||||
|
||||
CMD start.sh jupyter lab --ServerApp.open_browser=false --IdentityProvider.token=${JUPYTER_TOKEN:-mythic} --ServerApp.base_url="/jupyter" --ServerApp.default_url="/jupyter"
|
||||
# sudo docker run -p 8888:8888 -v `pwd`/jupyter:/projects jupyter
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.23 AS builder
|
||||
FROM golang:1.25 AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#FROM ghcr.io/its-a-feature/mythic_server:v0.0.3.8
|
||||
|
||||
FROM golang:1.23 AS builder
|
||||
FROM golang:1.25 AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ func ValidateLogin(username string, password string, scriptingVersion string, fr
|
||||
FROM operator
|
||||
WHERE username=$1`, username)
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to find username", "username", user.Username)
|
||||
logging.LogError(err, "Failed to find username", "username", username)
|
||||
return "", "", 0, err
|
||||
}
|
||||
if user.AccountType != databaseStructs.AccountTypeUser {
|
||||
@@ -33,14 +33,14 @@ func ValidateLogin(username string, password string, scriptingVersion string, fr
|
||||
err = errors.New("attempted log in with a deleted user")
|
||||
logging.LogError(err, "username", user.Username)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Attempted to log in with deleted user, %s, from %s", user.Username, fromIP),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_AUTH, true)
|
||||
return "", "", 0, err
|
||||
}
|
||||
if !user.Active {
|
||||
err = errors.New("Attempted log in with an inactive user")
|
||||
logging.LogError(err, "username", user.Username)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Attempted to log in with inactive user, %s, from %s", user.Username, fromIP),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_AUTH, true)
|
||||
return "", "", 0, err
|
||||
}
|
||||
if user.ID == 1 &&
|
||||
@@ -49,7 +49,7 @@ func ValidateLogin(username string, password string, scriptingVersion string, fr
|
||||
err = errors.New("Throttling login attempts of default account")
|
||||
logging.LogError(err, "Throttling login attempts of default account", "username", user.Username)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Throttling login attempts of account, %s, from %s", user.Username, fromIP),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_AUTH, true)
|
||||
return "", "", 0, err
|
||||
}
|
||||
if !database.CheckUserPassword(user, password) {
|
||||
@@ -62,7 +62,7 @@ func ValidateLogin(username string, password string, scriptingVersion string, fr
|
||||
user.Active = false
|
||||
logging.LogError(nil, "Deactivating user over too many failed login attempts", "username", user.Username)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Deactivating account due to failed attempts, %s, from %s", user.Username, fromIP),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_AUTH, true)
|
||||
}
|
||||
}
|
||||
updateUserLoginStatus(user)
|
||||
@@ -73,9 +73,9 @@ func ValidateLogin(username string, password string, scriptingVersion string, fr
|
||||
user.FailedLoginCount = 0
|
||||
updateUserLoginStatus(user)
|
||||
if scriptingVersion == "" {
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("%s logged in from %s", user.Username, fromIP), 0, "", database.MESSAGE_LEVEL_DEBUG)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("%s logged in from %s", user.Username, fromIP), 0, "", database.MESSAGE_LEVEL_AUTH, false)
|
||||
} else {
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("%s connected via Mythic Scripting (v%s) from %s", user.Username, scriptingVersion, fromIP), 0, "", database.MESSAGE_LEVEL_DEBUG)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("%s connected via Mythic Scripting (v%s) from %s", user.Username, scriptingVersion, fromIP), 0, "", database.MESSAGE_LEVEL_AUTH, false)
|
||||
}
|
||||
return mythicjwt.GenerateJWT(user, mythicjwt.AUTH_METHOD_USER, 0, 0)
|
||||
|
||||
@@ -97,14 +97,14 @@ func ValidateCustomAuthProviderLogin(email string, authError string, validLogin
|
||||
err = errors.New("attempted log in with a deleted user")
|
||||
logging.LogError(err, "username", user.Username)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Attempted to log in with deleted user, %s, from %s", user.Username, fromIP),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_AUTH, true)
|
||||
return "", "", 0, err
|
||||
}
|
||||
if !user.Active {
|
||||
err = errors.New("Attempted log in with an inactive user")
|
||||
logging.LogError(err, "username", user.Username)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Attempted to log in with inactive user, %s, from %s", user.Username, fromIP),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_AUTH, true)
|
||||
return "", "", 0, err
|
||||
}
|
||||
if user.ID == 1 &&
|
||||
@@ -113,7 +113,7 @@ func ValidateCustomAuthProviderLogin(email string, authError string, validLogin
|
||||
err = errors.New("Throttling login attempts of default account")
|
||||
logging.LogError(err, "Throttling login attempts of default account", "username", user.Username)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Throttling login attempts of account, %s, from %s", user.Username, fromIP),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_AUTH, true)
|
||||
return "", "", 0, err
|
||||
}
|
||||
if !validLogin {
|
||||
@@ -126,7 +126,7 @@ func ValidateCustomAuthProviderLogin(email string, authError string, validLogin
|
||||
user.Active = false
|
||||
logging.LogError(nil, "Deactivating user over too many failed login attempts", "username", user.Username)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Deactivating account due to failed attempts, %s, from %s", user.Username, fromIP),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_AUTH, true)
|
||||
}
|
||||
}
|
||||
updateUserLoginStatus(user)
|
||||
@@ -136,7 +136,7 @@ func ValidateCustomAuthProviderLogin(email string, authError string, validLogin
|
||||
user.LastLogin = sql.NullTime{Valid: true, Time: time.Now().UTC()}
|
||||
user.FailedLoginCount = 0
|
||||
updateUserLoginStatus(user)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("%s logged in from %s via %s's %s", user.Username, fromIP, containerName, IDPName), 0, "", database.MESSAGE_LEVEL_DEBUG)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("%s logged in from %s via %s's %s", user.Username, fromIP, containerName, IDPName), 0, "", database.MESSAGE_LEVEL_AUTH, false)
|
||||
return mythicjwt.GenerateJWT(user, mythicjwt.AUTH_METHOD_USER, 0, 0)
|
||||
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func TokenValid(c *gin.Context) error {
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Deactivated APIToken, %s, for user %s (%s) attempted to be used",
|
||||
token.Name, token.Operator.Username, token.Operator.AccountType),
|
||||
int(token.Operator.CurrentOperationID.Int64), "apitoken usage",
|
||||
database.MESSAGE_LEVEL_WARNING)
|
||||
database.MESSAGE_LEVEL_API, true)
|
||||
}(databaseApiToken)
|
||||
return errors.New("Deactivated APIToken attempted to be used")
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func TokenValid(c *gin.Context) error {
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Deleted APIToken, \"%s\", for user %s (%s) attempted to be used",
|
||||
token.Name, token.Operator.Username, token.Operator.AccountType),
|
||||
int(token.Operator.CurrentOperationID.Int64), token.Name+fmt.Sprintf("%d", token.ID)+token.Operator.Username,
|
||||
database.MESSAGE_LEVEL_WARNING)
|
||||
database.MESSAGE_LEVEL_API, true)
|
||||
}(databaseApiToken)
|
||||
return errors.New("Deleted APIToken attempted to be used")
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func IPBlockMiddleware() gin.HandlerFunc {
|
||||
|
||||
logging.LogError(nil, "Client IP not in allowed IP blocks", "client_ip", ipAddr)
|
||||
go rabbitmq.SendAllOperationsMessage(fmt.Sprintf("Client IP, %s, not in allowed IP blocks: %v", ipAddr.String(), utils.MythicConfig.AllowedIPBlocks),
|
||||
0, ipAddr.String(), database.MESSAGE_LEVEL_WARNING)
|
||||
0, ipAddr.String(), database.MESSAGE_LEVEL_AUTH, true)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"message": "Unauthorized"})
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@ var (
|
||||
OPERATOR_OPERATION_VIEW_MODE_OPERATOR = "operator"
|
||||
)
|
||||
|
||||
type MESSAGE_LEVEL = string
|
||||
type MESSAGE_TYPE = string
|
||||
|
||||
const (
|
||||
MESSAGE_LEVEL_INFO MESSAGE_LEVEL = "info"
|
||||
MESSAGE_LEVEL_WARNING = "warning"
|
||||
MESSAGE_LEVEL_DEBUG = "debug"
|
||||
MESSAGE_LEVEL_API = "api"
|
||||
MESSAGE_LEVEL_INFO MESSAGE_TYPE = "info"
|
||||
MESSAGE_LEVEL_AUTH = "auth"
|
||||
MESSAGE_LEVEL_DEBUG = "debug"
|
||||
MESSAGE_LEVEL_API = "api"
|
||||
MESSAGE_LEVEL_AGENT_MESSGAGE = "agent"
|
||||
MESSAGE_LEVEL_CHAT = "chat"
|
||||
)
|
||||
|
||||
@@ -8,9 +8,32 @@ alter table "public"."payloadtype" add column IF NOT EXISTS "supported_c2" jsonb
|
||||
alter table "public"."payloadtype" add column IF NOT EXISTS "semver" text not null default ''::text;
|
||||
alter table "public"."payloadtype" add column IF NOT EXISTS "command_help_function" text not null default ''::text;
|
||||
alter table "public"."c2profileparametersinstance" add column IF NOT EXISTS "count" integer not null default 0;
|
||||
alter table "public"."c2profile" add column IF NOT EXISTS "semver" text not null default ''::text;
|
||||
alter table "public"."c2profile" add column IF NOT EXISTS "has_logo" boolean not null default false;
|
||||
alter table "public"."consuming_container" add column IF NOT EXISTS "semver" text not null default ''::text;
|
||||
alter table "public"."operationeventlog" add column IF NOT EXISTS "warning" boolean not null default false;
|
||||
drop index if exists "public"."c2profileparametersinstance_c2_profile_parameters_id_ins_93bb57";
|
||||
drop index if exists "public"."c2profileparametersinstance_c2_profile_parameters_id_payload_id";
|
||||
|
||||
-- +migrate StatementBegin
|
||||
CREATE OR REPLACE FUNCTION public.update_operation_alert_count() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
current_max integer;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO current_max
|
||||
FROM operationeventlog
|
||||
WHERE operation_id = NEW.operation_id AND resolved=false AND deleted=false AND warning=true;
|
||||
|
||||
UPDATE operation SET alert_count = current_max WHERE id = NEW.operation_id;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- +migrate StatementEnd
|
||||
|
||||
-- +migrate Down
|
||||
-- SQL in section 'Down' is executed when this migration is rolled back
|
||||
|
||||
|
||||
@@ -15,4 +15,6 @@ type C2profile struct {
|
||||
IsP2p bool `db:"is_p2p"`
|
||||
IsServerRouted bool `db:"is_server_routed"`
|
||||
Deleted bool `db:"deleted"`
|
||||
SemVer string `db:"semver"`
|
||||
HasLogo bool `db:"has_logo"`
|
||||
}
|
||||
|
||||
@@ -12,4 +12,5 @@ type ConsumingContainer struct {
|
||||
ContainerRunning bool `db:"container_running" json:"container_running" toml:"container_running" yaml:"container_running"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
SemVer string `json:"semver" toml:"semver" yaml:"semver"`
|
||||
}
|
||||
|
||||
@@ -18,4 +18,5 @@ type Operationeventlog struct {
|
||||
Source string `db:"source"`
|
||||
Count int `db:"count"`
|
||||
APITokensID structs.NullInt64 `db:"apitokens_id" json:"api_tokens_id" mapstructure:"apitokens_id"`
|
||||
Warning bool `db:"warning"`
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ type Translationcontainer struct {
|
||||
Author string `db:"author"`
|
||||
Deleted bool `db:"deleted"`
|
||||
ContainerRunning bool `db:"container_running"`
|
||||
SemVer string `db:"semver"`
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ func CreateEventGroupInstance(eventGroupId int, trigger string, triggeringOperat
|
||||
return 0, errors.New(fmt.Sprintf("event group \"%s\" is deactivated and will not be triggered", eventGroup.Name))
|
||||
}
|
||||
if trigger != TriggerKeyword {
|
||||
if eventGroup.Trigger != trigger {
|
||||
if eventGroup.Trigger != trigger && trigger != TriggerManual {
|
||||
return 0, errors.New(fmt.Sprintf("event group \"%s\" doesn't have \"%s\" as a trigger", eventGroup.Name, trigger))
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,7 @@ type EventGroup struct {
|
||||
Environment map[string]interface{} `db:"environment" json:"environment" toml:"environment" yaml:"environment"`
|
||||
Keywords []string `db:"keywords" json:"keywords" toml:"keywords" yaml:"keywords"`
|
||||
RunAs string `db:"run_as" json:"run_as" toml:"run_as" yaml:"run_as"`
|
||||
Steps []EventStep `db:"eventstep" json:"steps" toml:"steps" yaml:"steps"`
|
||||
Steps []EventStep `db:"eventstep" json:"steps,omitempty" toml:"steps,omitempty" yaml:"steps,omitempty"`
|
||||
}
|
||||
|
||||
type EventStep struct {
|
||||
@@ -20,6 +20,6 @@ type EventStep struct {
|
||||
Environment map[string]interface{} `db:"environment" json:"environment" toml:"environment" yaml:"environment"`
|
||||
Inputs map[string]interface{} `db:"inputs" json:"inputs" toml:"inputs" yaml:"inputs"`
|
||||
Outputs map[string]interface{} `db:"outputs" json:"outputs" toml:"outputs" yaml:"outputs"`
|
||||
Order int `db:"order" json:"order" toml:"order" yaml:"order"`
|
||||
Order int `db:"order" json:"-" toml:"-" yaml:"-"`
|
||||
ContinueOnError bool `db:"continue_on_error" json:"continue_on_error" toml:"continue_on_error" yaml:"continue_on_error"`
|
||||
}
|
||||
|
||||
+33
-29
@@ -1,12 +1,11 @@
|
||||
module github.com/its-a-feature/Mythic
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.4
|
||||
go 1.25.1
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/go-co-op/gocron/v2 v2.16.2
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/go-co-op/gocron/v2 v2.16.6
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
@@ -16,49 +15,54 @@ require (
|
||||
github.com/rabbitmq/amqp091-go v1.10.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/rubenv/sql-migrate v1.8.0
|
||||
github.com/spf13/viper v1.20.1
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
||||
golang.org/x/mod v0.25.0
|
||||
google.golang.org/grpc v1.73.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
github.com/spf13/viper v1.21.0
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621
|
||||
golang.org/x/mod v0.28.0
|
||||
google.golang.org/grpc v1.75.1
|
||||
google.golang.org/protobuf v1.36.9
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.13.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.1 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.18.0 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.21.0 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect
|
||||
)
|
||||
|
||||
+80
-72
@@ -1,13 +1,13 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
|
||||
github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -16,18 +16,18 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-co-op/gocron/v2 v2.16.2 h1:r08P663ikXiulLT9XaabkLypL/W9MoCIbqgQoAutyX4=
|
||||
github.com/go-co-op/gocron/v2 v2.16.2/go.mod h1:4YTLGCCAH75A5RlQ6q+h+VacO7CgjkgP0EJ+BEOXRSI=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-co-op/gocron/v2 v2.16.6 h1:zI2Ya9sqvuLcgqJgV79LwoJXM8h20Z/drtB7ATbpRWo=
|
||||
github.com/go-co-op/gocron/v2 v2.16.6/go.mod h1:zAfC/GFQ668qHxOVl/D68Jh5Ce7sDqX6TJnSQyRkRBc=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
||||
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
@@ -36,14 +36,16 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
@@ -60,10 +62,8 @@ github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbd
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -95,6 +95,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
||||
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg=
|
||||
github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
@@ -106,28 +110,25 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o=
|
||||
github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw=
|
||||
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
@@ -136,47 +137,54 @@ github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
||||
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw=
|
||||
golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 h1:V1jCN2HBa8sySkR5vLcCSqJSTMv093Rw9EJefhQGP7M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
||||
@@ -276,7 +276,7 @@ func checkContainerStatus() {
|
||||
if !running {
|
||||
SendAllOperationsMessage(
|
||||
getDownContainerMessage(container),
|
||||
0, fmt.Sprintf("%s_container_down", container), "warning")
|
||||
0, fmt.Sprintf("%s_container_down", container), database.MESSAGE_LEVEL_INFO, true)
|
||||
go updateDownContainerBuildingPayloads(container)
|
||||
} else {
|
||||
go database.ResolveAllOperationsMessage(getDownContainerMessage(container), 0)
|
||||
@@ -309,7 +309,7 @@ func checkContainerStatus() {
|
||||
UpdateC2ProfileRunningStatus(c2profilesToCheck[container], false)
|
||||
SendAllOperationsMessage(
|
||||
getDownContainerMessage(container),
|
||||
0, fmt.Sprintf("%s_container_down", container), "warning")
|
||||
0, fmt.Sprintf("%s_container_down", container), database.MESSAGE_LEVEL_INFO, true)
|
||||
} else {
|
||||
go database.ResolveAllOperationsMessage(getDownContainerMessage(container), 0)
|
||||
go CreateGraphQLSpectatorAPITokenAndSendOnStartMessage(container)
|
||||
@@ -340,7 +340,7 @@ func checkContainerStatus() {
|
||||
if !running {
|
||||
SendAllOperationsMessage(
|
||||
getDownContainerMessage(container),
|
||||
0, fmt.Sprintf("%s_container_down", container), "warning")
|
||||
0, fmt.Sprintf("%s_container_down", container), database.MESSAGE_LEVEL_INFO, true)
|
||||
} else {
|
||||
go database.ResolveAllOperationsMessage(getDownContainerMessage(container), 0)
|
||||
go CreateGraphQLSpectatorAPITokenAndSendOnStartMessage(container)
|
||||
@@ -372,7 +372,7 @@ func checkContainerStatus() {
|
||||
if !running {
|
||||
SendAllOperationsMessage(
|
||||
getDownContainerMessage(container),
|
||||
0, fmt.Sprintf("%s_container_down", container), "warning")
|
||||
0, fmt.Sprintf("%s_container_down", container), database.MESSAGE_LEVEL_INFO, true)
|
||||
} else {
|
||||
go database.ResolveAllOperationsMessage(getDownContainerMessage(container), 0)
|
||||
go CreateGraphQLSpectatorAPITokenAndSendOnStartMessage(container)
|
||||
|
||||
@@ -104,6 +104,7 @@ const (
|
||||
PT_TASK_COMPLETION_FUNCTION = "pt_task_completion_function"
|
||||
//
|
||||
PT_TASK_PROCESS_RESPONSE = "pt_task_process_response"
|
||||
PT_COMMAND_HELP_FUNCTION = "pt_command_help_function"
|
||||
)
|
||||
|
||||
// Routes where container is consuming messages and responding back to Mythic
|
||||
|
||||
@@ -137,14 +137,14 @@ func listenForCronEvents() {
|
||||
if err != nil {
|
||||
logging.LogError(err, "failed to create new cron job")
|
||||
SendAllOperationsMessage(fmt.Sprintf("Failed to create new cron job\n%v", err),
|
||||
event.OperationID, "eventing_cron_listen", database.MESSAGE_LEVEL_WARNING)
|
||||
event.OperationID, "eventing_cron_listen", database.MESSAGE_LEVEL_INFO, true)
|
||||
continue
|
||||
}
|
||||
nextTriggerDate, err := j.NextRun()
|
||||
if err != nil {
|
||||
logging.LogError(err, "failed to get next run")
|
||||
SendAllOperationsMessage(fmt.Sprintf("Failed to get next cron job run\n%v", err),
|
||||
event.OperationID, "eventing_cron_next", database.MESSAGE_LEVEL_WARNING)
|
||||
event.OperationID, "eventing_cron_next", database.MESSAGE_LEVEL_INFO, true)
|
||||
continue
|
||||
}
|
||||
_, err = database.DB.Exec(`UPDATE eventgroup SET next_scheduled_run=$1 WHERE id=$2`,
|
||||
@@ -193,7 +193,7 @@ func listenForEvents() {
|
||||
case eventing.TriggerManual:
|
||||
// somebody manually triggered an event group to run
|
||||
eventgroupinstanceID, err := eventing.CreateEventGroupInstance(event.EventGroupID,
|
||||
eventing.TriggerManual, event.OperatorID, map[string]interface{}{})
|
||||
eventing.TriggerManual, event.OperatorID, event.KeywordEnvData)
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to create new event group instance")
|
||||
errMsg := err.Error()
|
||||
@@ -206,7 +206,7 @@ func listenForEvents() {
|
||||
logging.LogError(err, "Failed to get event group information")
|
||||
}
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Error triggering manual workflow: %s", errMsg),
|
||||
event.OperationID, source, database.MESSAGE_LEVEL_WARNING)
|
||||
event.OperationID, source, database.MESSAGE_LEVEL_INFO, true)
|
||||
continue
|
||||
}
|
||||
// still need to do something to start processing steps
|
||||
@@ -229,7 +229,7 @@ func listenForEvents() {
|
||||
logging.LogError(err, "Failed to get event group information")
|
||||
}
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Error retriggering workflow: %s", errMsg),
|
||||
event.OperationID, source, database.MESSAGE_LEVEL_WARNING)
|
||||
event.OperationID, source, database.MESSAGE_LEVEL_INFO, true)
|
||||
continue
|
||||
}
|
||||
case eventing.TriggerRetryFromStep:
|
||||
@@ -260,7 +260,7 @@ func listenForEvents() {
|
||||
logging.LogError(err, "Failed to get event group information")
|
||||
}
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Error triggering cron workflow: %s", errMsg),
|
||||
event.OperationID, source, database.MESSAGE_LEVEL_WARNING)
|
||||
event.OperationID, source, database.MESSAGE_LEVEL_INFO, true)
|
||||
continue
|
||||
}
|
||||
_, err = database.DB.Exec(`UPDATE eventgroup SET next_scheduled_run=$1 WHERE id=$2`,
|
||||
@@ -285,7 +285,7 @@ func listenForEvents() {
|
||||
source = fmt.Sprintf("Cancel Event Group Trigger: %s", eventGroup.EventGroup.Name)
|
||||
}
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Error cancelling workflow: %s", err.Error()),
|
||||
event.OperationID, source, database.MESSAGE_LEVEL_WARNING)
|
||||
event.OperationID, source, database.MESSAGE_LEVEL_INFO, true)
|
||||
}
|
||||
// payload starting to build triggers new things to start
|
||||
case eventing.TriggerPayloadBuildStart:
|
||||
@@ -412,7 +412,7 @@ func findEventGroupsToStart(eventNotification EventNotification) {
|
||||
source := fmt.Sprintf("Keyword Event Group Trigger: %s", eventNotification.Trigger)
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Error triggering keyword workflow for %s: %s",
|
||||
possibleEventGroups[i].Name, err.Error()),
|
||||
eventNotification.OperationID, source, database.MESSAGE_LEVEL_WARNING)
|
||||
eventNotification.OperationID, source, database.MESSAGE_LEVEL_INFO, true)
|
||||
continue
|
||||
}
|
||||
// still need to do something to start processing steps
|
||||
@@ -552,7 +552,7 @@ func findEventGroupsToStart(eventNotification EventNotification) {
|
||||
source := fmt.Sprintf("Event Group Trigger: %s", eventNotification.Trigger)
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Error triggering workflow for %s: %s",
|
||||
possibleEventGroups[i].Name, err.Error()),
|
||||
eventNotification.OperationID, source, database.MESSAGE_LEVEL_WARNING)
|
||||
eventNotification.OperationID, source, database.MESSAGE_LEVEL_INFO, true)
|
||||
continue
|
||||
}
|
||||
// still need to do something to start processing steps
|
||||
@@ -804,7 +804,7 @@ func startProcessingRunAgainGroupInstanceSteps(oldEventGroupInstanceID int, newO
|
||||
source := fmt.Sprintf("RunAgain Event Group Trigger: %s", oldEventGroupInstance.EventGroup.Name)
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Error triggering keyword workflow for %s: %s",
|
||||
oldEventGroupInstance.EventGroup.Name, err.Error()),
|
||||
oldEventGroupInstance.OperationID, source, database.MESSAGE_LEVEL_WARNING)
|
||||
oldEventGroupInstance.OperationID, source, database.MESSAGE_LEVEL_INFO, true)
|
||||
return
|
||||
}
|
||||
oldEventGroupInstance.ID = eventgroupinstanceID
|
||||
@@ -1264,7 +1264,12 @@ func startEventStepInstanceActionCreateAlert(eventStepInstance databaseStructs.E
|
||||
logging.LogError(err, "failed to decode action data")
|
||||
return err
|
||||
}
|
||||
go SendAllOperationsMessage(eventData.Alert, eventStepInstance.OperationID, eventData.Source, eventData.Level)
|
||||
warning := false
|
||||
if eventData.Level == "warning" {
|
||||
warning = true
|
||||
eventData.Level = database.MESSAGE_LEVEL_INFO
|
||||
}
|
||||
go SendAllOperationsMessage(eventData.Alert, eventStepInstance.OperationID, eventData.Source, eventData.Level, warning)
|
||||
if eventData.SendWebhook {
|
||||
databaseOperation := databaseStructs.Operation{ID: eventStepInstance.OperationID}
|
||||
err = database.DB.Get(&databaseOperation, `SELECT "name", "webhook", "channel" FROM operation WHERE id=$1`, databaseOperation.ID)
|
||||
|
||||
@@ -86,6 +86,7 @@ var pushC2StreamingDisconnectNotification = make(chan int, 100)
|
||||
func Initialize() {
|
||||
RabbitMQConnection.channelMutexMap = make(map[string]*channelMutex)
|
||||
go invalidateAllSpectatorAPITokens()
|
||||
go InvalidateOperationEventLogCacheMap()
|
||||
go listenForWriteDownloadChunkToLocalDisk()
|
||||
go listenForAsyncAgentMessagePostResponseContent()
|
||||
for {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/its-a-feature/Mythic/logging"
|
||||
"github.com/its-a-feature/Mythic/utils"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -27,11 +29,14 @@ type C2SyncMessage struct {
|
||||
}
|
||||
|
||||
type C2Profile struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Author string `json:"author"`
|
||||
IsP2p bool `json:"is_p2p"`
|
||||
IsServerRouted bool `json:"is_server_routed"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Author string `json:"author"`
|
||||
IsP2p bool `json:"is_p2p"`
|
||||
IsServerRouted bool `json:"is_server_routed"`
|
||||
SemVer string `json:"semver"`
|
||||
AgentIcon *[]byte `json:"agent_icon,omitempty"`
|
||||
DarkModeAgentIcon *[]byte `json:"dark_mode_agent_icon,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -90,7 +95,7 @@ func processC2SyncMessages(msg amqp.Delivery) interface{} {
|
||||
|
||||
if err := json.Unmarshal(msg.Body, &c2SyncMsg); err != nil {
|
||||
logging.LogError(err, "Failed to process c2 sync message")
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to sync c2 profile %s", err.Error()), 0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to sync c2 profile %s", err.Error()), 0, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
return C2SyncMessageResponse{Success: false, Error: err.Error()}
|
||||
} else {
|
||||
response := C2SyncMessageResponse{}
|
||||
@@ -98,7 +103,7 @@ func processC2SyncMessages(msg amqp.Delivery) interface{} {
|
||||
// failed to sync message
|
||||
response.Success = false
|
||||
response.Error = fmt.Sprintf("Error: %v", err)
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to sync %s - %s", c2SyncMsg.Profile.Name, err.Error()), 0, c2SyncMsg.Profile.Name, database.MESSAGE_LEVEL_WARNING)
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to sync %s - %s", c2SyncMsg.Profile.Name, err.Error()), 0, c2SyncMsg.Profile.Name, database.MESSAGE_LEVEL_INFO, true)
|
||||
} else {
|
||||
// successfully synced
|
||||
response.Success = true
|
||||
@@ -131,9 +136,13 @@ func c2Sync(in C2SyncMessage) error {
|
||||
c2Profile.IsServerRouted = in.Profile.IsServerRouted
|
||||
c2Profile.Description = in.Profile.Description
|
||||
c2Profile.Deleted = false
|
||||
c2Profile.SemVer = in.Profile.SemVer
|
||||
if in.Profile.AgentIcon != nil || in.Profile.DarkModeAgentIcon != nil {
|
||||
c2Profile.HasLogo = true
|
||||
}
|
||||
if statement, err := database.DB.PrepareNamed(`INSERT INTO c2profile
|
||||
("name",author,container_running,is_p2p,is_server_routed,description, running, deleted)
|
||||
VALUES (:name, :author, :container_running, :is_p2p, :is_server_routed, :description, :running, :deleted)
|
||||
("name",author,container_running,is_p2p,is_server_routed,description, running, deleted, has_logo, semver)
|
||||
VALUES (:name, :author, :container_running, :is_p2p, :is_server_routed, :description, :running, :deleted, :has_logo, :semver)
|
||||
RETURNING id`,
|
||||
); err != nil {
|
||||
logging.LogError(err, "Failed to create new c2 profile statement")
|
||||
@@ -156,9 +165,15 @@ func c2Sync(in C2SyncMessage) error {
|
||||
c2Profile.IsServerRouted = in.Profile.IsServerRouted
|
||||
c2Profile.Description = in.Profile.Description
|
||||
c2Profile.Deleted = false
|
||||
c2Profile.SemVer = in.Profile.SemVer
|
||||
if in.Profile.AgentIcon != nil || in.Profile.DarkModeAgentIcon != nil {
|
||||
c2Profile.HasLogo = true
|
||||
} else {
|
||||
c2Profile.HasLogo = false
|
||||
}
|
||||
_, err = database.DB.NamedExec(`UPDATE c2profile SET
|
||||
author=:author, container_running=:container_running, is_p2p=:is_p2p, is_server_routed=:is_server_routed,
|
||||
description=:description, running=:running, deleted=:deleted
|
||||
description=:description, running=:running, deleted=:deleted, has_logo=:has_logo, semver=:semver
|
||||
WHERE id=:id`, c2Profile,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -170,7 +185,7 @@ func c2Sync(in C2SyncMessage) error {
|
||||
logging.LogError(err, "Failed to sync C2 profile")
|
||||
return err
|
||||
}
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Successfully synced %s with container version %s", c2Profile.Name, in.ContainerVersion), 0, "debug", database.MESSAGE_LEVEL_DEBUG)
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Successfully synced %s with container version %s", c2Profile.Name, in.ContainerVersion), 0, "debug", database.MESSAGE_LEVEL_DEBUG, false)
|
||||
go database.ResolveAllOperationsMessage(getDownContainerMessage(c2Profile.Name), 0)
|
||||
go autoStartC2Profile(c2Profile)
|
||||
if newProfile {
|
||||
@@ -178,6 +193,41 @@ func c2Sync(in C2SyncMessage) error {
|
||||
}
|
||||
checkContainerStatusAddC2Channel <- c2Profile
|
||||
go CreateGraphQLSpectatorAPITokenAndSendOnStartMessage(c2Profile.Name)
|
||||
absPath, err := filepath.Abs(filepath.Join(".", "static", fmt.Sprintf("%s_light.svg", in.Profile.Name)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.Create(absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if in.Profile.AgentIcon != nil {
|
||||
if _, err = file.Write(*in.Profile.AgentIcon); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
file.Close()
|
||||
darkModeAbsPath, err := filepath.Abs(filepath.Join(".", "static", fmt.Sprintf("%s_dark.svg", in.Profile.Name)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
darkModeFile, err := os.Create(darkModeAbsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if in.Profile.DarkModeAgentIcon != nil {
|
||||
if _, err = darkModeFile.Write(*in.Profile.DarkModeAgentIcon); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if in.Profile.AgentIcon != nil {
|
||||
if _, err = darkModeFile.Write(*in.Profile.AgentIcon); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
darkModeFile.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -316,7 +366,7 @@ func autoStartC2Profile(c2Profile databaseStructs.C2profile) *C2StartServerMessa
|
||||
} else {
|
||||
UpdateC2ProfileRunningStatus(c2Profile, c2StartResp.InternalServerRunning)
|
||||
if !c2StartResp.InternalServerRunning {
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to start c2 profile %s:\n%s", c2Profile.Name, c2StartResp.Error), 0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to start c2 profile %s:\n%s", c2Profile.Name, c2StartResp.Error), 0, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,14 +404,14 @@ func autoReHostFiles(c2Profile databaseStructs.C2profile) {
|
||||
logging.LogError(err, "failed to send host file message to c2 profile")
|
||||
go SendAllOperationsMessage(fmt.Sprintf(
|
||||
"%s failed to start hosting file:\n%s", newTagMap["c2_profile"].(string),
|
||||
err.Error()), tag.Operation, "", database.MESSAGE_LEVEL_WARNING)
|
||||
err.Error()), tag.Operation, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
continue
|
||||
}
|
||||
if !c2HostFileResponse.Success {
|
||||
logging.LogError(err, "c2 profile failed to start hosting file")
|
||||
go SendAllOperationsMessage(fmt.Sprintf(
|
||||
"%s failed to start hosting file:\n%s", newTagMap["c2_profile"].(string),
|
||||
c2HostFileResponse.Error), tag.Operation, "", database.MESSAGE_LEVEL_WARNING)
|
||||
c2HostFileResponse.Error), tag.Operation, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ type ConsumingContainerDefinition struct {
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
Subscriptions []string `json:"subscriptions"`
|
||||
SemVer string `json:"semver"`
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -55,7 +56,7 @@ func processConsumingContainerSyncMessages(msg amqp.Delivery) interface{} {
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to process consuming container sync message")
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to sync consuming container profile %s", err.Error()),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
return ConsumingContainerSyncMessageResponse{Success: false, Error: err.Error()}
|
||||
}
|
||||
response := ConsumingContainerSyncMessageResponse{}
|
||||
@@ -65,7 +66,7 @@ func processConsumingContainerSyncMessages(msg amqp.Delivery) interface{} {
|
||||
response.Error = fmt.Sprintf("Error: %v", err)
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to sync %s - %s",
|
||||
c2SyncMsg.ConsumingContainer.Name, err.Error()), 0, c2SyncMsg.ConsumingContainer.Name,
|
||||
database.MESSAGE_LEVEL_WARNING)
|
||||
database.MESSAGE_LEVEL_INFO, true)
|
||||
} else {
|
||||
// successfully synced
|
||||
response.Success = true
|
||||
@@ -94,10 +95,11 @@ func consumingServicesSync(in ConsumingContainerSyncMessage) error {
|
||||
consumingContainer.Description = in.ConsumingContainer.Description
|
||||
consumingContainer.Deleted = false
|
||||
consumingContainer.Type = in.ConsumingContainer.Type
|
||||
consumingContainer.SemVer = in.ConsumingContainer.SemVer
|
||||
consumingContainer.Subscriptions = GetMythicJSONArrayFromStruct(in.ConsumingContainer.Subscriptions)
|
||||
if statement, err := database.DB.PrepareNamed(`INSERT INTO consuming_container
|
||||
("name",container_running,description,deleted,type,subscriptions)
|
||||
VALUES (:name, :container_running, :description, :deleted, :type, :subscriptions)
|
||||
("name",container_running,description,deleted,type,subscriptions, semver)
|
||||
VALUES (:name, :container_running, :description, :deleted, :type, :subscriptions, :semver)
|
||||
RETURNING id`,
|
||||
); err != nil {
|
||||
logging.LogError(err, "Failed to create new consuming_container statement")
|
||||
@@ -117,10 +119,11 @@ func consumingServicesSync(in ConsumingContainerSyncMessage) error {
|
||||
consumingContainer.Description = in.ConsumingContainer.Description
|
||||
consumingContainer.Deleted = false
|
||||
consumingContainer.Type = in.ConsumingContainer.Type
|
||||
consumingContainer.SemVer = in.ConsumingContainer.SemVer
|
||||
consumingContainer.Subscriptions = GetMythicJSONArrayFromStruct(in.ConsumingContainer.Subscriptions)
|
||||
_, err = database.DB.NamedExec(`UPDATE consuming_container SET
|
||||
container_running=:container_running, description=:description, deleted=:deleted,
|
||||
type=:type, subscriptions=:subscriptions
|
||||
type=:type, subscriptions=:subscriptions, semver=:semver
|
||||
WHERE id=:id`, consumingContainer,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -129,7 +132,7 @@ func consumingServicesSync(in ConsumingContainerSyncMessage) error {
|
||||
}
|
||||
}
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Successfully synced %s with container version %s",
|
||||
consumingContainer.Name, in.ContainerVersion), 0, "debug", database.MESSAGE_LEVEL_DEBUG)
|
||||
consumingContainer.Name, in.ContainerVersion), 0, "debug", database.MESSAGE_LEVEL_DEBUG, false)
|
||||
go database.ResolveAllOperationsMessage(getDownContainerMessage(consumingContainer.Name), 0)
|
||||
checkContainerStatusAddConsumingContainerChannel <- consumingContainer
|
||||
// update eventgroup consumingcontainer mappings
|
||||
|
||||
@@ -31,17 +31,17 @@ func processContainerOnStartMessageResponse(msg amqp.Delivery) {
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to process container on start response message")
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to process container on start response:\n %s", err.Error()),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
return
|
||||
}
|
||||
if containerStartResponse.Stderr != "" {
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to process %s on start response:\n %s",
|
||||
containerStartResponse.ContainerName, containerStartResponse.Stderr),
|
||||
0, fmt.Sprintf("%s_on_start", containerStartResponse.ContainerName), database.MESSAGE_LEVEL_WARNING)
|
||||
0, fmt.Sprintf("%s_on_start", containerStartResponse.ContainerName), database.MESSAGE_LEVEL_INFO, true)
|
||||
}
|
||||
if containerStartResponse.Stdout != "" {
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Container %s on start response:\n %s",
|
||||
containerStartResponse.ContainerName, containerStartResponse.Stdout),
|
||||
0, fmt.Sprintf("%s_on_start", containerStartResponse.ContainerName), database.MESSAGE_LEVEL_INFO)
|
||||
0, fmt.Sprintf("%s_on_start", containerStartResponse.ContainerName), database.MESSAGE_LEVEL_INFO, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func processEventingConditionalCheckResponse(msg amqp.Delivery) {
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to process eventing custom function response message")
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to process eventing conditional check response %s", err.Error()),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
return
|
||||
}
|
||||
eventingConditionalCheckProcess(c2SyncMsg)
|
||||
|
||||
@@ -34,7 +34,7 @@ func processEventingCustomFunctionResponse(msg amqp.Delivery) {
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to process eventing custom function response message")
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to process eventing custom function response %s", err.Error()),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
return
|
||||
}
|
||||
eventingCustomFunctionResponse(c2SyncMsg)
|
||||
|
||||
@@ -36,7 +36,7 @@ func processEventingResponseInterceptResponse(msg amqp.Delivery) {
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to process eventing response intercept response message")
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to process eventing response intercept response %s", err.Error()),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
return
|
||||
}
|
||||
_, err = database.DB.Exec(`UPDATE response SET response=$1, eventstepinstance_id=$2, timestamp=$4 WHERE id=$3`,
|
||||
@@ -44,7 +44,7 @@ func processEventingResponseInterceptResponse(msg amqp.Delivery) {
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to process eventing response intercept response message")
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to process eventing response intercept response %s", err.Error()),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
}
|
||||
taskID := 0
|
||||
err = database.DB.Get(&taskID, `SELECT task.id
|
||||
|
||||
@@ -39,7 +39,7 @@ func processEventingTaskInterceptResponse(msg amqp.Delivery) {
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to process eventing task intercept response message")
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to process eventing task intercept response %s", err.Error()),
|
||||
0, "", database.MESSAGE_LEVEL_WARNING)
|
||||
0, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
return
|
||||
}
|
||||
task := databaseStructs.Task{ID: input.TaskID}
|
||||
|
||||
@@ -44,10 +44,10 @@ func MythicRPCC2UpdateStatus(input MythicRPCC2UpdateStatusMessage) MythicRPCC2Up
|
||||
if input.Error != "" {
|
||||
if strings.HasPrefix(input.Error, "Server already") {
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Update from C2 Profile %s:\n%s\n", input.C2Profile, input.Error),
|
||||
0, fmt.Sprintf("%s_error", input.C2Profile), database.MESSAGE_LEVEL_DEBUG)
|
||||
0, fmt.Sprintf("%s_error", input.C2Profile), database.MESSAGE_LEVEL_DEBUG, false)
|
||||
} else {
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Error from C2 Profile %s:\n%s\n", input.C2Profile, input.Error),
|
||||
0, fmt.Sprintf("%s_error", input.C2Profile), database.MESSAGE_LEVEL_WARNING)
|
||||
0, fmt.Sprintf("%s_error", input.C2Profile), database.MESSAGE_LEVEL_INFO, true)
|
||||
}
|
||||
}
|
||||
return response
|
||||
|
||||
@@ -124,7 +124,7 @@ func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCall
|
||||
logging.LogError(nil, "Operation is complete, but payload still trying to create callback", "payload_uuid", input.PayloadUUID)
|
||||
go SendAllOperationsMessage(fmt.Sprintf(
|
||||
"Operation %s is complete, but payload %s is still trying to create a callback via %s\n", payload.Operation.Name, payload.UuID, input.C2ProfileName),
|
||||
0, fmt.Sprintf("complete_operation_%s", payload.UuID), database.MESSAGE_LEVEL_WARNING)
|
||||
0, fmt.Sprintf("complete_operation_%s", payload.UuID), database.MESSAGE_LEVEL_INFO, true)
|
||||
response.Error = "Operation is complete, but payload still trying to create callback"
|
||||
return response
|
||||
}
|
||||
@@ -312,7 +312,7 @@ func MythicRPCCallbackCreate(input MythicRPCCallbackCreateMessage) MythicRPCCall
|
||||
}
|
||||
addCommandAugmentsToCallback(callback.ID, payload.Os, payload.Payloadtype.Name, callback.OperatorID)
|
||||
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)
|
||||
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,
|
||||
|
||||
@@ -201,6 +201,9 @@ func MythicRPCCallbackSearch(input MythicRPCCallbackSearchMessage) MythicRPCCall
|
||||
result.RegisteredPayloadUUID = searchResults.Payload.UuID
|
||||
result.LockedOperatorID = int(searchResults.LockedOperatorID.Int64)
|
||||
result.PayloadType = searchResults.Payload.Payloadtype.Name
|
||||
result.InitCallback = searchResults.InitCallback
|
||||
result.Timestamp = searchResults.Timestamp
|
||||
result.LastCheckin = searchResults.LastCheckin
|
||||
response.Results = append(response.Results, result)
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,9 @@ type MythicRPCOperationEventLogCreateMessage struct {
|
||||
CallbackAgentId *string `json:"callback_agent_id"`
|
||||
OperationId *int `json:"operation_id"`
|
||||
// the data to store
|
||||
Message string `json:"message"`
|
||||
MessageLevel database.MESSAGE_LEVEL `json:"level"` //info or warning
|
||||
Message string `json:"message"`
|
||||
MessageLevel database.MESSAGE_TYPE `json:"level"` //info or warning
|
||||
Warning bool `json:"warning"`
|
||||
}
|
||||
type MythicRPCOperationEventLogCreateMessageResponse struct {
|
||||
Success bool `json:"success"`
|
||||
@@ -69,7 +70,11 @@ func MythicRPCOperationEventLogCreate(input MythicRPCOperationEventLogCreateMess
|
||||
} else if input.OperationId != nil {
|
||||
operationId = *input.OperationId
|
||||
}
|
||||
SendAllOperationsMessage(input.Message, operationId, "", input.MessageLevel)
|
||||
if input.MessageLevel == "warning" {
|
||||
input.MessageLevel = database.MESSAGE_LEVEL_INFO
|
||||
input.Warning = true
|
||||
}
|
||||
SendAllOperationsMessage(input.Message, operationId, "", input.MessageLevel, input.Warning)
|
||||
response.Success = true
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -265,9 +265,20 @@ func associateC2ProfilesWithPayload(databasePayload databaseStructs.Payload, c2P
|
||||
}
|
||||
// finalC2Profiles will look like {"http": {"callback_host": "val", "callback_port": val2}}
|
||||
finalC2Profiles := make([]PayloadBuildC2Profile, 0)
|
||||
payloadType := databasePayload.Payloadtype
|
||||
err := database.DB.Get(&payloadType, `SELECT c2_parameter_deviations FROM payloadtype WHERE id=$1`, databasePayload.PayloadTypeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deviations := make(map[string]map[string]C2ParameterDeviation)
|
||||
err = mapstructure.Decode(payloadType.C2ParameterDeviations.StructValue(), &deviations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for count, suppliedC2Profile := range *c2Profiles {
|
||||
databaseC2Profile := databaseStructs.C2profile{}
|
||||
if err := database.DB.Get(&databaseC2Profile, "SELECT * FROM c2profile WHERE name=$1 and deleted=false", suppliedC2Profile.Name); err != nil {
|
||||
err = database.DB.Get(&databaseC2Profile, "SELECT * FROM c2profile WHERE name=$1 and deleted=false", suppliedC2Profile.Name)
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to find c2 profile")
|
||||
return nil, err
|
||||
}
|
||||
@@ -277,18 +288,6 @@ func associateC2ProfilesWithPayload(databasePayload databaseStructs.Payload, c2P
|
||||
ID: databaseC2Profile.ID,
|
||||
Parameters: map[string]interface{}{},
|
||||
}
|
||||
/*
|
||||
if !databaseC2Profile.ContainerRunning {
|
||||
err := errors.New("C2 Profile container isn't running, can't task configuration checks")
|
||||
logging.LogError(err, "C2 Profile container isn't running, can't task configuration checks")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !databaseC2Profile.Running && !databaseC2Profile.IsP2p {
|
||||
// the profile isn't running, and it's not a P2P container, so issue the start task
|
||||
go autoStartC2Profile(databaseC2Profile)
|
||||
}
|
||||
*/
|
||||
databaseC2ProfileParameter := databaseStructs.C2profileparameters{}
|
||||
if rows, err := database.DB.NamedQuery(`SELECT
|
||||
*
|
||||
@@ -309,31 +308,55 @@ func associateC2ProfilesWithPayload(databasePayload databaseStructs.Payload, c2P
|
||||
if suppliedParameterName == databaseC2ProfileParameter.Name {
|
||||
// we have a supplied parameter that matches the one we're looking at from the database for this c2 profile
|
||||
found = true
|
||||
if strippedValue, err := GetFinalStringForDatabaseInstanceValueFromUserSuppliedValue(
|
||||
strippedValue, err := GetFinalStringForDatabaseInstanceValueFromUserSuppliedValue(
|
||||
databaseC2ProfileParameter.ParameterType, suppliedParameterValue,
|
||||
); err != nil {
|
||||
//if strippedValue, err := GetStrippedValueForC2Parameter(databaseC2ProfileParameter, suppliedParameterValue); err != nil {
|
||||
)
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to get stripped c2 profile parameter", "value", suppliedParameterValue)
|
||||
return nil, err
|
||||
} else {
|
||||
paramStringVal = strippedValue
|
||||
}
|
||||
paramStringVal = strippedValue
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if strippedValue, err := getFinalStringForDatabaseInstanceValueFromDefaultDatabaseString(
|
||||
databaseC2ProfileParameter.ParameterType, databaseC2ProfileParameter.DefaultValue,
|
||||
databaseC2ProfileParameter.Choices.StructValue(),
|
||||
databaseC2ProfileParameter.Randomize, databaseC2ProfileParameter.FormatString); err != nil {
|
||||
//if strippedValue, err := GetDefaultValueForC2Parameter(databaseC2ProfileParameter); err != nil {
|
||||
logging.LogError(err, "Failed to get default c2 profile parameter", "parameter", databaseC2ProfileParameter)
|
||||
return nil, err
|
||||
} else {
|
||||
paramStringVal = strippedValue
|
||||
if _, ok := deviations[suppliedC2Profile.Name]; ok {
|
||||
if _, ok = deviations[suppliedC2Profile.Name][databaseC2ProfileParameter.Name]; ok {
|
||||
if !deviations[suppliedC2Profile.Name][databaseC2ProfileParameter.Name].Supported {
|
||||
continue
|
||||
}
|
||||
updateStringParam := false
|
||||
if len(deviations[suppliedC2Profile.Name][databaseC2ProfileParameter.Name].Choices) > 0 {
|
||||
if !slices.Contains(deviations[suppliedC2Profile.Name][databaseC2ProfileParameter.Name].Choices, paramStringVal) {
|
||||
updateStringParam = true
|
||||
}
|
||||
} else if !found {
|
||||
updateStringParam = true
|
||||
}
|
||||
found = true
|
||||
if updateStringParam {
|
||||
strippedValue, err := GetFinalStringForDatabaseInstanceValueFromUserSuppliedValue(
|
||||
databaseC2ProfileParameter.ParameterType,
|
||||
deviations[suppliedC2Profile.Name][databaseC2ProfileParameter.Name].DefaultValue,
|
||||
)
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to get stripped c2 profile parameter", "value", deviations[suppliedC2Profile.Name][databaseC2ProfileParameter.Name].DefaultValue)
|
||||
return nil, err
|
||||
}
|
||||
paramStringVal = strippedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
strippedValue, err := getFinalStringForDatabaseInstanceValueFromDefaultDatabaseString(
|
||||
databaseC2ProfileParameter.ParameterType, databaseC2ProfileParameter.DefaultValue,
|
||||
databaseC2ProfileParameter.Choices.StructValue(),
|
||||
databaseC2ProfileParameter.Randomize, databaseC2ProfileParameter.FormatString)
|
||||
if err != nil {
|
||||
logging.LogError(err, "Failed to get default c2 profile parameter", "parameter", databaseC2ProfileParameter)
|
||||
return nil, err
|
||||
}
|
||||
paramStringVal = strippedValue
|
||||
}
|
||||
c2ParameterInstance := databaseStructs.C2profileparametersinstance{
|
||||
PayloadID: sql.NullInt64{Valid: true, Int64: int64(databasePayload.ID)},
|
||||
C2ProfileID: databaseC2Profile.ID,
|
||||
|
||||
@@ -49,6 +49,6 @@ func processOnNewCallbackResponse(msg amqp.Delivery) {
|
||||
return
|
||||
}
|
||||
go SendAllOperationsMessage(fmt.Sprintf("Failed to handle onNewCallback processing for callback %d\n%s", databaseCallback.DisplayID, newCallbackResponse.Error),
|
||||
databaseCallback.OperationID, "", database.MESSAGE_LEVEL_WARNING)
|
||||
databaseCallback.OperationID, "", database.MESSAGE_LEVEL_INFO, true)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user