diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 3512d44d2..0b59d44f6 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -4,6 +4,13 @@ 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.2.20-rc7] - 2024-03-29 + +### Changed + +- When payloads are built, files hosted, files written, or agent configurations checked, Mythic now restarts a C2 profile's server in case there were updates +- Added a fix for C2Profile Parameter Type of File + ## [3.2.20-rc6] - 2024-03-25 ### Changed diff --git a/mythic-docker/src/rabbitmq/recv_c2_sync.go b/mythic-docker/src/rabbitmq/recv_c2_sync.go index 3b8120a0b..55d33c883 100644 --- a/mythic-docker/src/rabbitmq/recv_c2_sync.go +++ b/mythic-docker/src/rabbitmq/recv_c2_sync.go @@ -43,6 +43,7 @@ const ( C2_PARAMETER_TYPE_DICTIONARY = "Dictionary" C2_PARAMETER_TYPE_NUMBER = "Number" C2_PARAMETER_TYPE_TYPED_ARRAY = "TypedArray" + C2_PARAMETER_TYPE_FILE = "File" ) type C2Parameter struct { diff --git a/mythic-docker/src/rabbitmq/send_c2_rpc_start_server.go b/mythic-docker/src/rabbitmq/send_c2_rpc_start_server.go index 0f8aa6513..367dfbd50 100644 --- a/mythic-docker/src/rabbitmq/send_c2_rpc_start_server.go +++ b/mythic-docker/src/rabbitmq/send_c2_rpc_start_server.go @@ -2,6 +2,7 @@ package rabbitmq import ( "encoding/json" + "github.com/its-a-feature/Mythic/database" "github.com/its-a-feature/Mythic/logging" ) @@ -21,6 +22,51 @@ type C2StartServerMessageResponse struct { InternalServerRunning bool `json:"server_running"` } +func RestartC2ServerAfterUpdate(c2ProfileName string, sendNotifications bool) { + go func() { + if sendNotifications { + go SendAllOperationsMessage("Stopping C2 Profile after hosting new file...", 0, "host_file", database.MESSAGE_LEVEL_INFO) + } + stopC2ProfileResponse, err := RabbitMQConnection.SendC2RPCStopServer(C2StopServerMessage{ + Name: c2ProfileName, + }) + if err != nil { + logging.LogError(err, "Failed to send RPC call to c2 profile in C2HostFileMessageWebhook", "c2_profile", c2ProfileName) + if sendNotifications { + go SendAllOperationsMessage("Failed to stop c2 profile after hosting file", 0, "host_file", database.MESSAGE_LEVEL_WARNING) + } + return + } + if !stopC2ProfileResponse.Success { + if sendNotifications { + go SendAllOperationsMessage(stopC2ProfileResponse.Error, 0, "", database.MESSAGE_LEVEL_WARNING) + } + return + } + if sendNotifications { + go SendAllOperationsMessage("Starting C2 Profile after hosting new file...", 0, "host_file", database.MESSAGE_LEVEL_INFO) + } + startC2ProfileResponse, err := RabbitMQConnection.SendC2RPCStartServer(C2StartServerMessage{ + Name: c2ProfileName, + }) + if err != nil { + logging.LogError(err, "Failed to send RPC call to c2 profile in C2HostFileMessageWebhook", "c2_profile", c2ProfileName) + if sendNotifications { + go SendAllOperationsMessage("Failed to start c2 profile after hosting file", 0, "", database.MESSAGE_LEVEL_WARNING) + } + return + } + if !startC2ProfileResponse.Success { + if sendNotifications { + go SendAllOperationsMessage(startC2ProfileResponse.Error, 0, "", database.MESSAGE_LEVEL_WARNING) + } + return + } + if sendNotifications { + go SendAllOperationsMessage("Successfully restarted C2 Profile after hosting a file", 0, "host_file", database.MESSAGE_LEVEL_INFO) + } + }() +} func (r *rabbitMQConnection) SendC2RPCStartServer(startServer C2StartServerMessage) (*C2StartServerMessageResponse, error) { c2StartServerResponse := C2StartServerMessageResponse{} exclusiveQueue := true diff --git a/mythic-docker/src/rabbitmq/send_pt_payload_build.go b/mythic-docker/src/rabbitmq/send_pt_payload_build.go index 64adc427f..29cf323dc 100644 --- a/mythic-docker/src/rabbitmq/send_pt_payload_build.go +++ b/mythic-docker/src/rabbitmq/send_pt_payload_build.go @@ -279,6 +279,7 @@ func SendPayloadBuildMessage(databasePayload databaseStructs.Payload, buildMessa checksPassed = false buildOutput += "[-] !!! C2 Configuration check failed !!! \n" + configCheckResponse.Error + "\n" } else { + go RestartC2ServerAfterUpdate(c2.Name, false) buildOutput += configCheckResponse.Message + "\n" } if !c2.IsP2P { diff --git a/mythic-docker/src/rabbitmq/util_agent_message.go b/mythic-docker/src/rabbitmq/util_agent_message.go index f667fa2f9..fcbe8b42a 100644 --- a/mythic-docker/src/rabbitmq/util_agent_message.go +++ b/mythic-docker/src/rabbitmq/util_agent_message.go @@ -291,7 +291,7 @@ func recursiveProcessAgentMessage(agentMessageInput AgentMessageRawInput) recurs } else if agentMessageInput.RawMessage != nil { base64DecodedMessage = *agentMessageInput.RawMessage } else { - errorMessage := "Failed toget message\n" + errorMessage := "Failed to get message\n" errorMessage += fmt.Sprintf("Connection from %s\n", agentMessageInput.RemoteIP) logging.LogError(err, "Failed to get agent message") go SendAllOperationsMessage(errorMessage, 0, "agent_message_base64", database.MESSAGE_LEVEL_WARNING) diff --git a/mythic-docker/src/rabbitmq/util_callback_graph.go b/mythic-docker/src/rabbitmq/util_callback_graph.go index d56e3335d..9a375986d 100644 --- a/mythic-docker/src/rabbitmq/util_callback_graph.go +++ b/mythic-docker/src/rabbitmq/util_callback_graph.go @@ -2,6 +2,7 @@ package rabbitmq import ( "database/sql" + "errors" "fmt" "github.com/its-a-feature/Mythic/database" databaseStructs "github.com/its-a-feature/Mythic/database/structs" @@ -199,14 +200,16 @@ func (g *cbGraph) AddByAgentIds(source string, destination string, c2profileName g.Add(sourceCallback, destinationCallback, c2profileName) g.Add(destinationCallback, sourceCallback, c2profileName) // can't have a unique constraint with a NULL value, NULL != NULL - if err := database.DB.Get(&edge.ID, `SELECT id FROM callbackgraphedge + err := database.DB.Get(&edge.ID, `SELECT id FROM callbackgraphedge WHERE operation_id=$1 AND source_id=$2 AND destination_id=$3 AND c2_profile_id=$4 AND end_timestamp IS NULL`, - edge.OperationID, edge.SourceID, edge.DestinationID, edge.C2ProfileID); err == sql.ErrNoRows { + edge.OperationID, edge.SourceID, edge.DestinationID, edge.C2ProfileID) + if errors.Is(err, sql.ErrNoRows) { // this specific combination didn't yield any results, so add it - if _, err := database.DB.NamedExec(`INSERT INTO callbackgraphedge + _, err := database.DB.NamedExec(`INSERT INTO callbackgraphedge (operation_id, source_id, destination_id, c2_profile_id) - VALUES (:operation_id, :source_id, :destination_id, :c2_profile_id)`, edge); err != nil { + VALUES (:operation_id, :source_id, :destination_id, :c2_profile_id)`, edge) + if err != nil { logging.LogError(err, "Failed to insert new edge for P2P connection") } else { logging.LogInfo("added new callbackgraph edge when updating graph by agent ids") diff --git a/mythic-docker/src/rabbitmq/utils.go b/mythic-docker/src/rabbitmq/utils.go index 910dab173..3d0496555 100644 --- a/mythic-docker/src/rabbitmq/utils.go +++ b/mythic-docker/src/rabbitmq/utils.go @@ -393,6 +393,8 @@ func getFinalStringForDatabaseInstanceValueFromDefaultDatabaseString(parameterTy } else { return strings.TrimSpace(defaultValue), nil } + case BUILD_PARAMETER_TYPE_FILE: + fallthrough case BUILD_PARAMETER_TYPE_STRING: return strings.TrimSpace(defaultValue), nil case BUILD_PARAMETER_TYPE_CHOOSE_MULTIPLE: diff --git a/mythic-docker/src/webserver/controllers/c2profile_config_check_webhook.go b/mythic-docker/src/webserver/controllers/c2profile_config_check_webhook.go index 048fbf614..ad25b0c65 100644 --- a/mythic-docker/src/webserver/controllers/c2profile_config_check_webhook.go +++ b/mythic-docker/src/webserver/controllers/c2profile_config_check_webhook.go @@ -93,6 +93,7 @@ func C2ProfileConfigCheckWebhook(c *gin.Context) { } else { output += fmt.Sprintf("Configuration Check for %s\n%s\n", c2ProfileName, c2ConfigCheckMessageResponse.Message) } + go rabbitmq.RestartC2ServerAfterUpdate(c2ProfileName, false) } c.JSON(http.StatusOK, GetC2ConfigCheckResponse{ Status: "success", diff --git a/mythic-docker/src/webserver/controllers/c2profile_host_file_webhook.go b/mythic-docker/src/webserver/controllers/c2profile_host_file_webhook.go index 155b5924e..5a4c6f871 100644 --- a/mythic-docker/src/webserver/controllers/c2profile_host_file_webhook.go +++ b/mythic-docker/src/webserver/controllers/c2profile_host_file_webhook.go @@ -82,35 +82,7 @@ func C2HostFileMessageWebhook(c *gin.Context) { }) return } - go func() { - go rabbitmq.SendAllOperationsMessage("Stopping C2 Profile after hosting new file...", 0, "host_file", database.MESSAGE_LEVEL_INFO) - stopC2ProfileResponse, err := rabbitmq.RabbitMQConnection.SendC2RPCStopServer(rabbitmq.C2StopServerMessage{ - Name: c2Profile.Name, - }) - if err != nil { - logging.LogError(err, "Failed to send RPC call to c2 profile in C2HostFileMessageWebhook", "c2_profile", c2Profile.Name) - go rabbitmq.SendAllOperationsMessage("Failed to stop c2 profile after hosting file", 0, "host_file", database.MESSAGE_LEVEL_WARNING) - return - } - if !stopC2ProfileResponse.Success { - go rabbitmq.SendAllOperationsMessage(stopC2ProfileResponse.Error, 0, "", database.MESSAGE_LEVEL_WARNING) - return - } - go rabbitmq.SendAllOperationsMessage("Starting C2 Profile after hosting new file...", 0, "host_file", database.MESSAGE_LEVEL_INFO) - startC2ProfileResponse, err := rabbitmq.RabbitMQConnection.SendC2RPCStartServer(rabbitmq.C2StartServerMessage{ - Name: c2Profile.Name, - }) - if err != nil { - logging.LogError(err, "Failed to send RPC call to c2 profile in C2HostFileMessageWebhook", "c2_profile", c2Profile.Name) - go rabbitmq.SendAllOperationsMessage("Failed to start c2 profile after hosting file", 0, "", database.MESSAGE_LEVEL_WARNING) - return - } - if !startC2ProfileResponse.Success { - go rabbitmq.SendAllOperationsMessage(startC2ProfileResponse.Error, 0, "", database.MESSAGE_LEVEL_WARNING) - return - } - go rabbitmq.SendAllOperationsMessage("Successfully restarted C2 Profile after hosting a file", 0, "host_file", database.MESSAGE_LEVEL_INFO) - }() + go rabbitmq.RestartC2ServerAfterUpdate(c2Profile.Name, true) c.JSON(http.StatusOK, C2HostFileMessageResponse{ Status: "success", Error: "", diff --git a/mythic-docker/src/webserver/controllers/c2profile_write_file_webhook.go b/mythic-docker/src/webserver/controllers/c2profile_write_file_webhook.go index 3b28cb163..a3c4f7869 100644 --- a/mythic-docker/src/webserver/controllers/c2profile_write_file_webhook.go +++ b/mythic-docker/src/webserver/controllers/c2profile_write_file_webhook.go @@ -40,7 +40,8 @@ func C2ProfileWriteFileWebhook(c *gin.Context) { // get the associated database information c2profile := databaseStructs.C2profile{} - if err := database.DB.Get(&c2profile, `SELECT "name" FROM c2profile WHERE id=$1`, input.Input.C2ProfileID); err != nil { + err := database.DB.Get(&c2profile, `SELECT "name" FROM c2profile WHERE id=$1`, input.Input.C2ProfileID) + if err != nil { logging.LogError(err, "Failed to fetch c2 profile from database by ID", "id", input.Input.C2ProfileID) c.JSON(http.StatusOK, WriteContainerFileResponse{ Status: "error", @@ -48,18 +49,22 @@ func C2ProfileWriteFileWebhook(c *gin.Context) { }) return // send the RPC request to the container - } else if fileContents, err := base64.StdEncoding.DecodeString(input.Input.Data); err != nil { + } + fileContents, err := base64.StdEncoding.DecodeString(input.Input.Data) + if err != nil { logging.LogError(err, "Failed to base64 decode file contents") c.JSON(http.StatusOK, WriteContainerFileResponse{ Status: "error", Error: "Failed to base64 decode file contents", }) return - } else if c2ProfileResponse, err := rabbitmq.RabbitMQConnection.SendC2RPCWriteFile(rabbitmq.C2WriteFileMessage{ + } + c2ProfileResponse, err := rabbitmq.RabbitMQConnection.SendC2RPCWriteFile(rabbitmq.C2WriteFileMessage{ Filename: input.Input.Filename, Name: c2profile.Name, Contents: fileContents, - }); err != nil { + }) + if err != nil { logging.LogError(err, "Failed to send C2ProfileWriteFileWebhook to c2 profile", "c2_profile", c2profile.Name) c.JSON(http.StatusOK, WriteContainerFileResponse{ Status: "error", @@ -67,18 +72,19 @@ func C2ProfileWriteFileWebhook(c *gin.Context) { }) return // check the response from the RPC call for success or error - } else if !c2ProfileResponse.Success { + } + if !c2ProfileResponse.Success { logging.LogError(nil, c2ProfileResponse.Error, "Failed to write file to c2 container") c.JSON(http.StatusOK, WriteContainerFileResponse{ Status: "error", Error: c2ProfileResponse.Error, }) return - } else { - - c.JSON(http.StatusOK, WriteContainerFileResponse{ - Status: "success", - }) - return } + go rabbitmq.RestartC2ServerAfterUpdate(c2profile.Name, false) + c.JSON(http.StatusOK, WriteContainerFileResponse{ + Status: "success", + }) + return + }