Skip to content

Commit

Permalink
mythic-cli agent install updates and slight ui tweak
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Mar 4, 2025
1 parent 68c2005 commit 1366ebe
Show file tree
Hide file tree
Showing 16 changed files with 185 additions and 14 deletions.
6 changes: 6 additions & 0 deletions MythicReactUI/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.3.15] - 2025-03-04

### Changed

- Added additional error messages on login

## [0.3.14] - 2025-02-26

### Changed
Expand Down
13 changes: 13 additions & 0 deletions MythicReactUI/src/components/pages/Login/LoginForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ export function LoginForm(props){
body: JSON.stringify({username, password})
};
fetch('/auth', requestOptions).then((response) => {
if(response.status === 403){
snackActions.warning("Invalid username or password");
return;
}else if(response.status === 404){
snackActions.warning("Failed to find login endpoint");
return;
}else if(response.status === 502){
snackActions.warning("Failed to contact mythic server due to gateway. Please refresh");
return;
}else if(response.status === 400){
snackActions.warning("Bad format, can't process request");
return;
}
if(response.status !== 200){
snackActions.warning("HTTP " + response.status + " Error: Check Mythic logs");
return;
Expand Down
4 changes: 3 additions & 1 deletion MythicReactUI/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import jwt_decode from 'jwt-decode';
import {meState} from './cache';
import {getSkewedNow} from "./components/utilities/Time";

export const mythicUIVersion = "0.3.14";
export const mythicUIVersion = "0.3.15";

let fetchingNewToken = false;

Expand Down Expand Up @@ -174,6 +174,8 @@ const errorLink = onError(({ graphQLErrors, networkError }) => {
snackActions.error(err.message);
break;
case 'access-denied':
snackActions.error(err.message);
break;
//fallsthrough
case 'start-failed':
// when AuthenticationError thrown in resolver
Expand Down
8 changes: 8 additions & 0 deletions Mythic_CLI/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ 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.14 - 2025-03-04

### Changed

- Added a `-i` flag for `update -s [service]` and `update -a` to auto install updates for installed services
- Updated the "port in use" check when starting Mythic to prevent port forwards for all services except the UI
- Added a `rabbitmq reset` command to delete the rabbitmq-docker/storage folder or volume

## 0.3.13 - 2025-02-18

### Changed
Expand Down
2 changes: 1 addition & 1 deletion Mythic_CLI/src/cmd/config/vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ package config

var (
// Version Mythic CLI version
Version = "v0.3.13"
Version = "v0.3.14"
)
3 changes: 2 additions & 1 deletion Mythic_CLI/src/cmd/internal/installservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,8 @@ func InstallFolder(installPath string, overWrite bool, keepVolume bool, installU
if manager.GetManager().IsServiceRunning("mythic_documentation") {
log.Printf("[*] Restarting mythic_documentation container to pull in changes\n")
ServiceStop([]string{"mythic_documentation"}, true)
ServiceStart([]string{"mythic_documentation"}, true)
manager.GetManager().BuildServices([]string{"mythic_documentation"}, true)
//ServiceStart([]string{"mythic_documentation"}, true)
}
return nil
}
Expand Down
18 changes: 16 additions & 2 deletions Mythic_CLI/src/cmd/internal/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ func DatabaseReset(force bool) {
if force {
log.Printf("[*] Stopping Mythic\n")
manager.GetManager().StopServices([]string{}, config.GetMythicEnv().GetBool("REBUILD_ON_START"), false)
manager.GetManager().ResetDatabase(config.GetMythicEnv().GetBool("postgres_use_volume"))
log.Printf("[*] Removing database files\n")
manager.GetManager().ResetDatabase(config.GetMythicEnv().GetBool("postgres_use_volume"))
return
}
confirm := config.AskConfirm("Are you sure you want to reset the database? ")
Expand All @@ -20,11 +20,25 @@ func DatabaseReset(force bool) {
if confirm {
log.Printf("[*] Stopping Mythic\n")
manager.GetManager().StopServices([]string{}, config.GetMythicEnv().GetBool("REBUILD_ON_START"), false)
manager.GetManager().ResetDatabase(config.GetMythicEnv().GetBool("postgres_use_volume"))
log.Printf("[*] Removing database files\n")
manager.GetManager().ResetDatabase(config.GetMythicEnv().GetBool("postgres_use_volume"))
}
}
}
func RabbitmqReset(force bool) {
if force {
manager.GetManager().StopServices([]string{"mythic_rabbitmq"}, config.GetMythicEnv().GetBool("REBUILD_ON_START"), false)
log.Printf("[*] Removing rabbitmq storage files\n")
manager.GetManager().ResetRabbitmq(config.GetMythicEnv().GetBool("rabbitmq_use_volume"))
return
}
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)
log.Printf("[*] Removing rabbitmq storage files\n")
manager.GetManager().ResetRabbitmq(config.GetMythicEnv().GetBool("rabbitmq_use_volume"))
}
}

func DatabaseBackup(backupPath string) {
confirm := config.AskConfirm("Are you sure you want to backup the database? ")
Expand Down
42 changes: 42 additions & 0 deletions Mythic_CLI/src/cmd/manager/dockerComposeManager.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,20 @@ func (d *DockerComposeManager) GetLogs(service string, logCount int, follow bool
log.Println("[-] No containers running")
}
}
func checkPortRedirection(address string) (bool, string, error) {
dialer := net.Dialer{Timeout: 1 * time.Second}
conn, err := dialer.Dial("tcp", address)
if err != nil {
return false, "", err
}
defer conn.Close()

resolvedAddress := conn.RemoteAddr().String()
if resolvedAddress != address {
return true, resolvedAddress, nil
}
return true, "", nil
}
func (d *DockerComposeManager) TestPorts(services []string) {
// go through the different services in mythicEnv and check to make sure their ports aren't already used by trying to open them
//MYTHIC_SERVER_HOST:MYTHIC_SERVER_PORT
Expand Down Expand Up @@ -635,6 +648,18 @@ func (d *DockerComposeManager) TestPorts(services []string) {
if err != nil {
log.Printf("[-] Failed to close connection: %v\n", err)
}
isOpen, redirectedAddress, err := checkPortRedirection(":" + strconv.Itoa(mythicEnv.GetInt(val[0])))
if err != nil {
continue
}
// allow port forwards for the Mythic UI but nothing else
if isOpen && val[1] != "mythic_nginx" {
if redirectedAddress != "" {
log.Fatalf("Port %s is in use and redirected to %s\n", ":"+strconv.Itoa(mythicEnv.GetInt(val[0])), redirectedAddress)
} else {
log.Fatalf("Port %s is in use by something else\n", ":"+strconv.Itoa(mythicEnv.GetInt(val[0])))
}
}
} else {
removeServices = append(removeServices, val[1])
}
Expand Down Expand Up @@ -907,6 +932,23 @@ func (d *DockerComposeManager) ResetDatabase(useVolume bool) {
}
}
}
func (d *DockerComposeManager) ResetRabbitmq(useVolume bool) {
if !useVolume {
workingPath := utils.GetCwdFromExe()
err := os.RemoveAll(filepath.Join(workingPath, "rabbitmq-docker", "storage"))
if err != nil {
log.Fatalf("[-] Failed to remove rabbitmq storage files\n%v\n", err)
} else {
log.Printf("[+] Successfully reset rabbitmq storage files\n")
}
} else {
_ = d.RemoveContainers([]string{"mythic_rabbitmq"}, false)
err := d.RemoveVolume("mythic_rabbitmq_volume")
if err != nil {
log.Printf("[-] Failed to remove rabbitmq storage volume:\n%v\n", err)
}
}
}
func (d *DockerComposeManager) BackupDatabase(backupPath string, useVolume bool) error {
if !useVolume {
workingPath := utils.GetCwdFromExe()
Expand Down
2 changes: 2 additions & 0 deletions Mythic_CLI/src/cmd/manager/managerInterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ type CLIManager interface {
PrintAllServices()
// ResetDatabase deletes the current database or volume
ResetDatabase(useVolume bool)
// ResetRabbitmq deletes the current rabbitmq storage or volume
ResetRabbitmq(useVolume bool)
// BackupDatabase saves a copy of the database to the specified path
BackupDatabase(backupPath string, useVolume bool) error
// RestoreDatabase restores a saved copy of the database from the specified path
Expand Down
29 changes: 29 additions & 0 deletions Mythic_CLI/src/cmd/rabbitmqReset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cmd

import (
"github.com/MythicMeta/Mythic_CLI/cmd/internal"
"github.com/spf13/cobra"
)

// configCmd represents the config command
var rabbitmqResetCmd = &cobra.Command{
Use: "reset",
Short: "reset the rabbitmq storage",
Long: `Run this command to delete the current rabbitmq storage cache. Rabbitmq will recreate this on startup.`,
Run: rabbitmqReset,
}

func init() {
rabbitmqCmd.AddCommand(rabbitmqResetCmd)
rabbitmqResetCmd.Flags().BoolVarP(
&force,
"force",
"f",
false,
`Force deleting the rabbitmq storage and don't prompt for confirmation`,
)
}

func rabbitmqReset(cmd *cobra.Command, args []string) {
internal.RabbitmqReset(force)
}
56 changes: 55 additions & 1 deletion Mythic_CLI/src/cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var updateCmd = &cobra.Command{
}
var agents []string
var allAgents bool
var installAgentUpdates bool

func init() {
rootCmd.AddCommand(updateCmd)
Expand All @@ -52,6 +53,13 @@ func init() {
false,
`Check for potential updates for all installed services instead of Mythic`,
)
updateCmd.Flags().BoolVarP(
&installAgentUpdates,
"install-agent-updates",
"i",
false,
`Force install updates for installed services if they exist`,
)
}

func updateCheck(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -281,6 +289,7 @@ func checkConfigJsonVersion(body []byte, agentName string) (updateVersion string
return "", nil
}
func checkAgentVersions(agents []string, allAgents bool) error {
agentUpdateMessages := ""
localAgents := agents
if len(localAgents) == 0 || allAgents {
dockerComposeContainers, err := manager.GetManager().GetAllInstalled3rdPartyServiceNames()
Expand All @@ -293,54 +302,99 @@ func checkAgentVersions(agents []string, allAgents bool) error {
installLocation := config.GetMythicEnv().GetString(fmt.Sprintf("%s_install_location", agent))
if installLocation == "" {
log.Printf("[-] No tracked install location for %s, install again to start tracking location\n", agent)
agentUpdateMessages += fmt.Sprintf("[-] No tracked install location for %s, install again to start tracking location\n", agent)
continue
}
if strings.HasPrefix(installLocation, "http") {
// check a remote location
targetInfoPieces := strings.Split(installLocation, ";")
if len(targetInfoPieces) != 2 {
log.Printf("[!] Invalid install location for agent %s: %s\n", agent, installLocation)
agentUpdateMessages += fmt.Sprintf("[!] Invalid install location for agent %s: %s\n", agent, installLocation)
continue
}
workingPath, err := internal.GitClone(targetInfoPieces[0], targetInfoPieces[1])
if err != nil {
log.Printf("[!] Failed to clone working path for %s: %v\n", agent, err)
agentUpdateMessages += fmt.Sprintf("[!] Failed to clone working path for %s: %v\n", agent, err)
continue
}
configDataBytes, err := os.ReadFile(filepath.Join(workingPath, "config.json"))
internal.RemoveGitClone()
if err != nil {
log.Printf("[!] Failed to read config.json for %s: %v\n", agent, err)
agentUpdateMessages += fmt.Sprintf("[!] Failed to read config.json for %s: %v\n", agent, err)
continue
}
needsUpdate, err := checkConfigJsonVersion(configDataBytes, agent)
if err != nil {
log.Printf("[!] Failed to parse config.json for %s: %v\n", agent, err)
agentUpdateMessages += fmt.Sprintf("[!] Failed to parse config.json for %s: %v\n", agent, err)
continue
}
if needsUpdate != "" {
log.Printf("[+] %s has an update available! %s\n", agent, needsUpdate)
agentUpdateMessages += fmt.Sprintf("[+] %s has an update available! %s\n", agent, needsUpdate)
if installAgentUpdates {
localKeepVolume := keepVolume
if !keepVolume {
localKeepVolume = !config.GetMythicEnv().GetBool("REBUILD_ON_START")
}
err = internal.InstallService(targetInfoPieces[0], targetInfoPieces[1], true, localKeepVolume)
if err != nil {
log.Printf("[!] Failed to install updated %s: %v\n", agent, err)
agentUpdateMessages += fmt.Sprintf("[!] Failed to install updated %s: %v\n", agent, err)
} else {
log.Printf("[+] Successfully installed updated %s\n", agent)
agentUpdateMessages += fmt.Sprintf("[+] Successfully installed updated %s\n", agent)
}
}
} else {
log.Printf("[*] %s is up to date!\n", agent)
log.Printf("[*] %s is up to date! Version: %s\n", agent, config.GetMythicEnv().GetString(fmt.Sprintf("%s_remote_image", agent)))
agentUpdateMessages += fmt.Sprintf("[*] %s is up to date! Version: %s\n", agent, config.GetMythicEnv().GetString(fmt.Sprintf("%s_remote_image", agent)))
}
} else {
// checking a folder on disk
configDataBytes, err := os.ReadFile(filepath.Join(installLocation, "config.json"))
if err != nil {
log.Printf("[!] Failed to read config.json for %s: %v\n", agent, err)
agentUpdateMessages += fmt.Sprintf("[!] Failed to read config.json for %s: %v\n", agent, err)
continue
}
needsUpdate, err := checkConfigJsonVersion(configDataBytes, agent)
if err != nil {
log.Printf("[!] Failed to parse config.json for %s: %v\n", agent, err)
agentUpdateMessages += fmt.Sprintf("[!] Failed to parse config.json for %s: %v\n", agent, err)
continue
}
if needsUpdate != "" {
log.Printf("[+] %s has an update available! %s\n", agent, needsUpdate)
agentUpdateMessages += fmt.Sprintf("[+] %s has an update available! %s\n", agent, needsUpdate)
if installAgentUpdates {
localKeepVolume := keepVolume
if !keepVolume {
localKeepVolume = !config.GetMythicEnv().GetBool("REBUILD_ON_START")
}
err = internal.InstallFolder(installLocation, true, localKeepVolume, "")
if err != nil {
log.Printf("[!] Failed to parse config.json for %s: %v\n", agent, err)
agentUpdateMessages += fmt.Sprintf("[!] Failed to parse config.json for %s: %v\n", agent, err)
continue
} else {
log.Printf("[+] Successfully installed updated %s\n", agent)
agentUpdateMessages += fmt.Sprintf("[+] Successfully installed updated %s\n", agent)
}
}
} else {
log.Printf("[*] %s is up to date!\n", agent)
agentUpdateMessages += fmt.Sprintf("[*] %s is up to date!\n", agent)
}
}
}
if installAgentUpdates {
agentUpdateMessages = fmt.Sprintf("----------------\n[*] Update Summary\n----------------\n") + agentUpdateMessages
fmt.Print(agentUpdateMessages)
}

return nil
}
6 changes: 3 additions & 3 deletions mythic-react-docker/mythic/public/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"files": {
"main.css": "/new/static/css/main.602591e6.css",
"main.js": "/new/static/js/main.7f18fd93.js",
"main.js": "/new/static/js/main.da33a068.js",
"static/media/mythic-red.png": "/new/static/media/mythic-red.203468a4e5240d239aa0.png",
"static/media/graphql.png": "/new/static/media/graphql.8f15978b39b0870a9f0e.png",
"static/media/Mythic_Logo.svg": "/new/static/media/Mythic_Logo.6842c911bebe36d6f83fc7ced4a2cd99.svg",
"static/media/mythic_red_small.svg": "/new/static/media/mythic_red_small.793b41cc7135cdede246661ec232976b.svg",
"index.html": "/new/index.html",
"main.602591e6.css.map": "/new/static/css/main.602591e6.css.map",
"main.7f18fd93.js.map": "/new/static/js/main.7f18fd93.js.map"
"main.da33a068.js.map": "/new/static/js/main.da33a068.js.map"
},
"entrypoints": [
"static/css/main.602591e6.css",
"static/js/main.7f18fd93.js"
"static/js/main.da33a068.js"
]
}
2 changes: 1 addition & 1 deletion mythic-react-docker/mythic/public/index.html
Original file line number Diff line number Diff line change
@@ -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.7f18fd93.js"></script><link href="/new/static/css/main.602591e6.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.da33a068.js"></script><link href="/new/static/css/main.602591e6.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

Large diffs are not rendered by default.

Large diffs are not rendered by default.

0 comments on commit 1366ebe

Please sign in to comment.