mirror of
https://github.com/its-a-feature/Mythic
synced 2026-06-08 14:55:38 +00:00
bump v3.0.0rc49
added JSON output for reports and updated RPC calls to allow updating of callback process_name
This commit is contained in:
+9
-1
@@ -4,7 +4,15 @@ 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.0.0rc48] - 2024-04-25
|
||||
## [3.0.0rc49] - 2023-04-26
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed an issue with marking payloads as deleted when linking agents
|
||||
- Updated the UI for tasking dropdown boxes are full width
|
||||
- Updated reporting function to generate JSON output in addition to XML
|
||||
|
||||
## [3.0.0rc48] - 2023-04-25
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
@@ -12,14 +12,13 @@ import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import {TaskParametersDialogRow} from './TaskParametersDialogRow';
|
||||
import {useQuery, gql, useLazyQuery, useMutation } from '@apollo/client';
|
||||
import {gql, useLazyQuery, useMutation, useQuery} from '@apollo/client';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Input from '@mui/material/Input';
|
||||
import {UploadTaskFile} from '../../MythicComponents/MythicFileUpload';
|
||||
import { Backdrop } from '@mui/material';
|
||||
import {CircularProgress} from '@mui/material';
|
||||
import {Backdrop, CircularProgress} from '@mui/material';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import {useTheme} from '@mui/material/styles';
|
||||
import {b64DecodeUnicode} from './ResponseDisplay';
|
||||
@@ -146,6 +145,7 @@ query getAllPayloadsOnHostsQuery($operation_id: Int!){
|
||||
payload {
|
||||
auto_generated
|
||||
id
|
||||
operation_id
|
||||
description
|
||||
filemetum {
|
||||
filename_text
|
||||
@@ -213,13 +213,13 @@ const addPayloadOnHostMutation = gql`
|
||||
`;
|
||||
// use this to remove a payload on a host
|
||||
const removePayloadOnHostMutation = gql`
|
||||
mutation removePayloadOnHostMutation($payloadOnHostID: Int!){
|
||||
delete_payloadonhost_by_pk(id: $payloadOnHostID) {
|
||||
id
|
||||
mutation removePayloadOnHostMutation($payload_id: Int!, $host: String!, $operation_id: Int!){
|
||||
update_payloadonhost(where: {host: {_eq: $host}, payload_id: {_eq: $payload_id}, operation_id: {_eq: $operation_id}}, _set: {deleted: true}) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
`;
|
||||
// use this to get all of the parameters and information for the command we're trying to execute
|
||||
// use this to get all the parameters and information for the command we're trying to execute
|
||||
const getCommandQuery = gql`
|
||||
query getCommandQuery($id: Int!){
|
||||
command_by_pk(id: $id) {
|
||||
@@ -392,8 +392,12 @@ export function TaskParametersDialog(props) {
|
||||
return {...prev};
|
||||
}
|
||||
}, {});
|
||||
let agentConnectValue = {host: choice.host, agent_uuid: choice.payload.uuid, callback_uuid: choice.agent_callback_id, c2_profile: {name: choices[0]["c2profile"]["name"], parameters: c2profileparameters} };
|
||||
return agentConnectValue;
|
||||
return {
|
||||
host: choice.host,
|
||||
agent_uuid: choice.payload.uuid,
|
||||
callback_uuid: choice.agent_callback_id,
|
||||
c2_profile: {name: choices[0]["c2profile"]["name"], parameters: c2profileparameters}
|
||||
};
|
||||
}else{
|
||||
return {};
|
||||
}
|
||||
@@ -404,7 +408,7 @@ export function TaskParametersDialog(props) {
|
||||
(!requiredPieces["payloads"] || loadedAllPayloadsLoading) &&
|
||||
(!requiredPieces["connect"] || loadedAllPayloadsOnHostsLoading) &&
|
||||
(!requiredPieces["credentials"] || loadedCredentialsLoading) ){
|
||||
//only process the parameter once we have fetched all of the required pieces
|
||||
//only process the parameter once we have fetched all the required pieces
|
||||
const params = rawParameters.command_by_pk.commandparameters.reduce( (prev, cmd) => {
|
||||
if(cmd.parameter_group_name !== selectedParameterGroup){
|
||||
return [...prev];
|
||||
@@ -527,12 +531,20 @@ export function TaskParametersDialog(props) {
|
||||
return [...prevn, profile.c2profile.name];
|
||||
}, []).join(",");
|
||||
if(foundP2P){
|
||||
return [...prevn, {...payload, display: b64DecodeUnicode(payload.filemetum.filename_text) + " - " + profiles + " - " + payload.description}];
|
||||
return [...prevn, {...payload, display: b64DecodeUnicode(payload.filemetum.filename_text) + " - " + profiles + " - " + payload.description,
|
||||
filemetum: {filename_text: b64DecodeUnicode(payload.filemetum.filename_text)}}];
|
||||
}else{
|
||||
return [...prevn];
|
||||
}
|
||||
|
||||
}, []);
|
||||
}, []).sort((a,b) => {
|
||||
if(a.filemetum.filename_text === b.filemetum.filename_text){
|
||||
return a.id < b.id ? 1 : -1
|
||||
}else{
|
||||
return a.filemetum.filename_text < b.filemetum.filename_text ? 1 : -1
|
||||
}
|
||||
});
|
||||
|
||||
const callbacksOrganized = loadedAllPayloadsOnHostsLoading.callback.reduce( (prevn, entry) => {
|
||||
let found = false;
|
||||
const updates = prevn.map( (host) => {
|
||||
@@ -584,34 +596,47 @@ export function TaskParametersDialog(props) {
|
||||
const updates = prevn.map( (host) => {
|
||||
if(host.host === entry.host){
|
||||
found = true;
|
||||
// need to check for entries that exist within host.payload but not loadedAllPayloadsOnHostsLoading.payloadonhost
|
||||
// this would mean that the payload was deleted
|
||||
//now we need to merge this entry with our current payloads/callbacks for the host
|
||||
let duplicated_payload = false;
|
||||
host.payloads.forEach( (p) => {
|
||||
if(p.id === entry.payload.id){duplicated_payload = true}
|
||||
});
|
||||
if(duplicated_payload){return host}
|
||||
// what was fetched doesn't exist in the current list
|
||||
const c2info = entry.payload.c2profileparametersinstances.reduce( (prevn, cur) => {
|
||||
const val = !cur.c2profileparameter.crypto_type ? cur.value : {crypto_type: cur.value, enc_key: cur.enc_key_base64, dec_key: cur.dec_key_base64};
|
||||
if(cur.c2profile.name in prevn){
|
||||
//we just want to add a new entry to the c2profile.name list
|
||||
|
||||
return {...prevn, [cur.c2profile.name]: [...prevn[cur.c2profile.name], { name: cur.c2profileparameter.name, value: val } ] }
|
||||
}else{
|
||||
return {...prevn, [cur.c2profile.name]: [ { name: cur.c2profileparameter.name, value: val } ] }
|
||||
}
|
||||
}, {});
|
||||
const val = !cur.c2profileparameter.crypto_type ? cur.value : {crypto_type: cur.value, enc_key: cur.enc_key_base64, dec_key: cur.dec_key_base64};
|
||||
if(cur.c2profile.name in prevn){
|
||||
//we just want to add a new entry to the c2profile.name list
|
||||
|
||||
return {...prevn, [cur.c2profile.name]: [...prevn[cur.c2profile.name], { name: cur.c2profileparameter.name, value: val } ] }
|
||||
}else{
|
||||
return {...prevn, [cur.c2profile.name]: [ { name: cur.c2profileparameter.name, value: val } ] }
|
||||
}
|
||||
}, {});
|
||||
let c2array = [];
|
||||
for( const [key, value] of Object.entries(c2info)){
|
||||
c2array.push({name: key, parameters: value});
|
||||
}
|
||||
const payloadInfo = {...entry.payload, c2info: c2array, display: b64DecodeUnicode(entry.payload.filemetum.filename_text) + " - " + entry.payload.description, type: "payload", payloadOnHostID:entry.id};
|
||||
return {...host, payloads: [...host.payloads, payloadInfo]}
|
||||
const payloadInfo = {...entry.payload, c2info: c2array,
|
||||
display: b64DecodeUnicode(entry.payload.filemetum.filename_text) + " - " + entry.payload.description,
|
||||
type: "payload", payloadOnHostID:entry.id, filemetum: {filename_text: b64DecodeUnicode(entry.payload.filemetum.filename_text)}
|
||||
};
|
||||
return {...host, payloads: [...host.payloads, payloadInfo].sort((a,b) => {
|
||||
if(a.filemetum.filename_text === b.filemetum.filename_text){
|
||||
return a.id < b.id ? 1 : -1
|
||||
}else{
|
||||
return a.filemetum.filename_text < b.filemetum.filename_text ? 1 : -1
|
||||
}
|
||||
})}
|
||||
}else{
|
||||
//this doesn't match our host, so don't modify
|
||||
return host;
|
||||
}
|
||||
});
|
||||
if(!found){
|
||||
// did even find the host, so add a new host entry
|
||||
const c2info = entry.payload.c2profileparametersinstances.reduce( (prevn, cur) => {
|
||||
const val = !cur.c2profileparameter.crypto_type ? cur.value : {crypto_type: cur.value, enc_key: cur.enc_key_base64, dec_key: cur.dec_key_base64};
|
||||
if(cur.c2profile.name in prevn){
|
||||
@@ -626,14 +651,17 @@ export function TaskParametersDialog(props) {
|
||||
for( const [key, value] of Object.entries(c2info)){
|
||||
c2array.push({name: key, parameters: value});
|
||||
}
|
||||
const payloadInfo = {...entry.payload, c2info: c2array, display: b64DecodeUnicode(entry.payload.filemetum.filename_text) + " - " + entry.payload.description, type: "payload", payloadOnHostID:entry.id};
|
||||
const payloadInfo = {...entry.payload, c2info: c2array,
|
||||
display: b64DecodeUnicode(entry.payload.filemetum.filename_text) + " - " + entry.payload.description,
|
||||
type: "payload", payloadOnHostID:entry.id,
|
||||
filemetum: {filename_text: b64DecodeUnicode(entry.payload.filemetum.filename_text)}};
|
||||
return [...prevn, {host: entry.host, payloads: [payloadInfo] } ]
|
||||
}else{
|
||||
return updates;
|
||||
}
|
||||
}, []);
|
||||
// callbacksOrganized has all the information for active callbacks to link to
|
||||
// organized has all of the information for payloads on hosts to link to
|
||||
// organized has all the information for payloads on hosts to link to
|
||||
// need to merge the two
|
||||
const allOrganized = callbacksOrganized.reduce( (prevn, cur) => {
|
||||
let hostIndex = prevn.findIndex(o => o.host === cur.host);
|
||||
@@ -668,12 +696,22 @@ export function TaskParametersDialog(props) {
|
||||
}
|
||||
}
|
||||
if(matched){
|
||||
return [...prevn, {...payload, display: b64DecodeUnicode(payload.filemetum.filename_text) + " - " + profiles + " - " + payload.description}]
|
||||
return [...prevn, {...payload,
|
||||
display: b64DecodeUnicode(payload.filemetum.filename_text) + " - " + profiles + " - " + payload.description,
|
||||
filemetum: {filename_text: b64DecodeUnicode(payload.filemetum.filename_text)}
|
||||
}]
|
||||
}else{
|
||||
return prevn;
|
||||
}
|
||||
|
||||
}, []);
|
||||
payloads.sort((a,b) => {
|
||||
if(a.filemetum.filename_text === b.filemetum.filename_text){
|
||||
return a.id < b.id ? 1 : -1
|
||||
}else{
|
||||
return a.filemetum.filename_text < b.filemetum.filename_text ? 1 : -1
|
||||
}
|
||||
});
|
||||
//now filter the payloads based on supported_agents and supported_agent_build_parameters
|
||||
if(payloads.length > 0){
|
||||
return [...prev, {...cmd, choices: payloads, default_value: payloads[0].uuid, value: payloads[0].uuid}];
|
||||
@@ -751,8 +789,8 @@ export function TaskParametersDialog(props) {
|
||||
const onAgentConnectAddNewPayloadOnHost = (host, payload) => {
|
||||
addPayloadOnHost({variables: {host: host, payload_id: payload} })
|
||||
}
|
||||
const onAgentConnectRemovePayloadOnHost = (payloadOnHostID) => {
|
||||
RemovePayloadOnHost({variables: {payloadOnHostID: payloadOnHostID}})
|
||||
const onAgentConnectRemovePayloadOnHost = ({payload, host}) => {
|
||||
RemovePayloadOnHost({variables: {host: host, payload_id: payload.id, operation_id: payload.operation_id}})
|
||||
}
|
||||
const onChange = (name, value, error) => {
|
||||
//console.log("called props.onChange to update a value for submission, have these parameters: ", [...parameters]);
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, {useEffect} from 'react';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import Select from '@mui/material/Select';
|
||||
@@ -208,7 +207,7 @@ export function TaskParametersDialogRow(props){
|
||||
if(props.dynamic_query_function === null && value===""){
|
||||
setChoiceOptions([...props.choices]);
|
||||
setValue(props.value);
|
||||
}else if(props.choices.length != ChoiceOptions.length){
|
||||
}else if(props.choices.length !== ChoiceOptions.length){
|
||||
if(!usingDynamicParamChoices.current){
|
||||
setChoiceOptions([...props.choices]);
|
||||
}
|
||||
@@ -337,7 +336,7 @@ export function TaskParametersDialogRow(props){
|
||||
}
|
||||
const onAgentConnectRemovePayloadOnHost = () => {
|
||||
if(props.choices[agentConnectHost]["payloads"][agentConnectPayload].payloadOnHostID){
|
||||
props.onAgentConnectRemovePayloadOnHost(props.choices[agentConnectHost]["payloads"][agentConnectPayload].payloadOnHostID);
|
||||
props.onAgentConnectRemovePayloadOnHost({payload: props.choices[agentConnectHost]["payloads"][agentConnectPayload], host: agentConnectHostOptions[agentConnectHost].host});
|
||||
}else{
|
||||
snackActions.warning("Can't remove a callback");
|
||||
}
|
||||
@@ -461,7 +460,7 @@ export function TaskParametersDialogRow(props){
|
||||
)
|
||||
case "LinkInfo":
|
||||
return (
|
||||
<FormControl>
|
||||
<FormControl style={{width: "100%"}}>
|
||||
<Select
|
||||
native
|
||||
value={value}
|
||||
@@ -479,13 +478,14 @@ export function TaskParametersDialogRow(props){
|
||||
)
|
||||
case "PayloadList":
|
||||
return (
|
||||
<FormControl>
|
||||
<FormControl style={{width: "100%"}}>
|
||||
<Select
|
||||
native
|
||||
value={value}
|
||||
|
||||
autoFocus={props.autoFocus}
|
||||
onChange={onChangeValue}
|
||||
input={<Input />}
|
||||
input={<Input />}
|
||||
>
|
||||
{
|
||||
props.choices.map((opt, i) => (
|
||||
@@ -512,7 +512,7 @@ export function TaskParametersDialogRow(props){
|
||||
<TableRow>
|
||||
<MythicStyledTableCell>Payload on that host</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<FormControl>
|
||||
<FormControl style={{width: "100%"}}>
|
||||
<Select
|
||||
native
|
||||
value={agentConnectNewPayload}
|
||||
@@ -543,7 +543,7 @@ export function TaskParametersDialogRow(props){
|
||||
Host
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<FormControl>
|
||||
<FormControl style={{width: "100%"}}>
|
||||
<Select
|
||||
native
|
||||
value={agentConnectHost}
|
||||
@@ -562,7 +562,7 @@ export function TaskParametersDialogRow(props){
|
||||
<TableRow>
|
||||
<MythicStyledTableCell>Payload</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<FormControl>
|
||||
<FormControl style={{width: "100%"}}>
|
||||
<Select
|
||||
native
|
||||
value={agentConnectPayload}
|
||||
@@ -590,7 +590,7 @@ export function TaskParametersDialogRow(props){
|
||||
<TableRow>
|
||||
<MythicStyledTableCell>C2 Profile</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
<FormControl>
|
||||
<FormControl style={{width: "100%"}}>
|
||||
<Select
|
||||
native
|
||||
value={agentConnectC2Profile}
|
||||
@@ -629,7 +629,7 @@ export function TaskParametersDialogRow(props){
|
||||
) ) }
|
||||
</TableBody>
|
||||
</Table>
|
||||
): (null)}
|
||||
): null}
|
||||
</TableContainer>
|
||||
)
|
||||
case "CredentialJson":
|
||||
@@ -672,7 +672,7 @@ export function TaskParametersDialogRow(props){
|
||||
</MythicStyledTooltip>
|
||||
{props.required ? (
|
||||
<Typography component="div" style={{color: theme.palette.warning.main}}>Required</Typography>
|
||||
) : (null) }
|
||||
) : null }
|
||||
</MythicStyledTableCell>
|
||||
<MythicStyledTableCell>
|
||||
{getParameterObject()}
|
||||
|
||||
@@ -16,8 +16,8 @@ import MessageTypes from 'subscriptions-transport-ws/dist/message-types';
|
||||
import jwt_decode from 'jwt-decode';
|
||||
import {meState} from './cache';
|
||||
|
||||
export const mythicVersion = "3.0.0rc48";
|
||||
export const mythicUIVersion = "0.1.0";
|
||||
export const mythicVersion = "3.0.0rc49";
|
||||
export const mythicUIVersion = "0.1.2";
|
||||
|
||||
let fetchingNewToken = false;
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ type Mutation {
|
||||
type Mutation {
|
||||
sendExternalWebhook(
|
||||
webhook_type: String!
|
||||
webhook_data: [DictionaryEntry!]!
|
||||
webhook_data: jsonb!
|
||||
): sendExternalWebhookOutput
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ type MythicRPCCallbackUpdateMessage struct {
|
||||
Domain *string `json:"domain,omitempty"`
|
||||
Architecture *string `json:"architecture,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
ProcessName *string `json:"process_name,omitempty"`
|
||||
}
|
||||
type MythicRPCCallbackUpdateMessageResponse struct {
|
||||
Success bool `json:"success"`
|
||||
@@ -145,10 +146,13 @@ func MythicRPCCallbackUpdate(input MythicRPCCallbackUpdateMessage) MythicRPCCall
|
||||
if input.Description != nil {
|
||||
callback.Description = *input.Description
|
||||
}
|
||||
if input.ProcessName != nil {
|
||||
callback.ProcessName = *input.ProcessName
|
||||
}
|
||||
if _, err := database.DB.NamedExec(`UPDATE callback SET
|
||||
"user"=:user, host=:host, pid=:pid, ip=:ip, extra_info=:extra_info, sleep_info=:sleep_info, enc_key=:enc_key, dec_key=:dec_key,
|
||||
crypto_type=:crypto_type, external_ip=:external_ip, integrity_level=:integrity_level, os=:os, domain=:domain, architecture=:architecture,
|
||||
description=:description
|
||||
description=:description, process_name=:process_name
|
||||
WHERE id=:id`, callback); err != nil {
|
||||
logging.LogError(err, "Failed to update callback information")
|
||||
response.Error = err.Error()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/new/static/css/main.4722e92b.css",
|
||||
"main.js": "/new/static/js/main.12a6d858.js",
|
||||
"main.js": "/new/static/js/main.8c1c13a9.js",
|
||||
"static/media/mythic.svg": "/new/static/media/mythic.7189479fdfbd51ae729872ce2cb98784.svg",
|
||||
"static/media/mythic_red_small.svg": "/new/static/media/mythic_red_small.793b41cc7135cdede246661ec232976b.svg",
|
||||
"index.html": "/new/index.html",
|
||||
"main.4722e92b.css.map": "/new/static/css/main.4722e92b.css.map",
|
||||
"main.12a6d858.js.map": "/new/static/js/main.12a6d858.js.map"
|
||||
"main.8c1c13a9.js.map": "/new/static/js/main.8c1c13a9.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.4722e92b.css",
|
||||
"static/js/main.12a6d858.js"
|
||||
"static/js/main.8c1c13a9.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.12a6d858.js"></script><link href="/new/static/css/main.4722e92b.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.8c1c13a9.js"></script><link href="/new/static/css/main.4722e92b.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
+3
-3
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user