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:
its-a-feature
2023-04-27 11:31:58 -05:00
parent 04936d39eb
commit 42e98b1d7c
13 changed files with 792 additions and 432 deletions
+9 -1
View File
@@ -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()}
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -1 +1 @@
3.0.0rc48
3.0.0rc49
+1 -1
View File
@@ -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
View File
@@ -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>