From 2024537cd5aefbc06c1207d0d4c7287bf73c00a1 Mon Sep 17 00:00:00 2001 From: its-a-feature Date: Thu, 23 Apr 2026 14:49:30 -0500 Subject: [PATCH] prevent duplicate onStart calls for the same container --- MythicReactUI/src/themes/GlobalStyles.js | 12 ++--------- .../src/rabbitmq/check_container_status.go | 20 ++++++++++++++++--- .../recv_mythic_rpc_apitoken_create.go | 3 ++- mythic-docker/src/rabbitmq/utils.go | 16 +++++++++------ 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/MythicReactUI/src/themes/GlobalStyles.js b/MythicReactUI/src/themes/GlobalStyles.js index 907683c6..d87dbcdd 100644 --- a/MythicReactUI/src/themes/GlobalStyles.js +++ b/MythicReactUI/src/themes/GlobalStyles.js @@ -3,10 +3,12 @@ import { createGlobalStyle} from "styled-components" export const GlobalStyles = createGlobalStyle` body { margin: 0 + } html, body, #root { height: 100%; width: 100%; + color-scheme: light dark; } @media screen and (max-width: 1100px) { .hideOnSmallWidth { @@ -186,16 +188,6 @@ tspan { .selectedTask { background-color: ${(props) => props.theme.selectedCallbackColor + "DD"} !important; } -*::-webkit-scrollbar { - width: 0.4em; - height: 0.4em; -} -*::-webkit-scrollbar-track { - -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.00); -} -*::-webkit-scrollbar-thumb { - background-color: ${(props) => props.theme.palette.secondary.dark}; -} .MythicResizableGrid-headerCellRow { display: flex; flex-direction: row; diff --git a/mythic-docker/src/rabbitmq/check_container_status.go b/mythic-docker/src/rabbitmq/check_container_status.go index 9c479c0d..ffbeb93b 100644 --- a/mythic-docker/src/rabbitmq/check_container_status.go +++ b/mythic-docker/src/rabbitmq/check_container_status.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "sync" "time" "github.com/its-a-feature/Mythic/authentication/mythicjwt" @@ -27,6 +28,8 @@ var checkContainerStatusAddConsumingContainerChannel = make(chan databaseStructs var consumingContainersToCheck = map[string]databaseStructs.ConsumingContainer{} var checkContainerStatusAddCustomBrowserChannel = make(chan databaseStructs.CustomBrowser) var customBrowsersToCheck = map[string]databaseStructs.CustomBrowser{} +var containerOnStartLock sync.Mutex +var containerOnStartInFlight = map[string]bool{} func checkContainerStatusAddPT() { for { @@ -123,6 +126,14 @@ type rabbitmqAPIQuery struct { } func CreateGraphQLSpectatorAPITokenAndSendOnStartMessage(containerName string) { + containerOnStartLock.Lock() + if containerOnStartInFlight[containerName] { + containerOnStartLock.Unlock() + logging.LogInfo("Skipping duplicate container on start run already in progress", "container", containerName) + return + } + containerOnStartInFlight[containerName] = true + containerOnStartLock.Unlock() operations := []databaseStructs.Operation{} err := database.DB.Select(&operations, `SELECT id FROM operation WHERE deleted=false and complete=false`) if err != nil { @@ -201,7 +212,7 @@ func CreateGraphQLSpectatorAPITokenAndSendOnStartMessage(containerName string) { continue } onStartMessage.APIToken = plainAPITokenValue - go updateAPITokenAfter5Minutes(apiToken.ID) + go updateAPITokenAfter5Minutes(apiToken.ID, containerName) err = RabbitMQConnection.SendContainerOnStart(onStartMessage) if err != nil { logging.LogError(err, "Failed to send container on start") @@ -210,12 +221,15 @@ func CreateGraphQLSpectatorAPITokenAndSendOnStartMessage(containerName string) { time.Sleep(5 * time.Second) } } -func updateAPITokenAfter5Minutes(apitoken_id int) { +func updateAPITokenAfter5Minutes(apitokenID int, containerName string) { <-time.After(5 * time.Minute) - _, err := database.DB.Exec(`UPDATE apitokens SET active=false, deleted=true WHERE id=$1`, apitoken_id) + _, err := database.DB.Exec(`UPDATE apitokens SET active=false, deleted=true WHERE id=$1`, apitokenID) if err != nil { logging.LogError(err, "failed to mark apitoken as deleted") } + containerOnStartLock.Lock() + delete(containerOnStartInFlight, containerName) + containerOnStartLock.Unlock() } func checkContainerStatus() { // get all queues from rabbitmq diff --git a/mythic-docker/src/rabbitmq/recv_mythic_rpc_apitoken_create.go b/mythic-docker/src/rabbitmq/recv_mythic_rpc_apitoken_create.go index 45786775..ba297196 100644 --- a/mythic-docker/src/rabbitmq/recv_mythic_rpc_apitoken_create.go +++ b/mythic-docker/src/rabbitmq/recv_mythic_rpc_apitoken_create.go @@ -3,6 +3,7 @@ package rabbitmq import ( "encoding/json" "fmt" + "github.com/its-a-feature/Mythic/authentication/mythicjwt" "github.com/its-a-feature/Mythic/database" databaseStructs "github.com/its-a-feature/Mythic/database/structs" @@ -138,7 +139,7 @@ func MythicRPCAPITokenCreate(input MythicRPCAPITokenCreateMessage) MythicRPCAPIT response.APIToken = accessToken if apiToken.TokenType == mythicjwt.AUTH_METHOD_GRAPHQL_SPECTATOR { // deactivate the token after 5 min (should be a short-lived use) - go updateAPITokenAfter5Minutes(apiToken.ID) + go updateAPITokenAfter5Minutes(apiToken.ID, response.APIToken) } return response } diff --git a/mythic-docker/src/rabbitmq/utils.go b/mythic-docker/src/rabbitmq/utils.go index 9c8293ba..275ebd65 100644 --- a/mythic-docker/src/rabbitmq/utils.go +++ b/mythic-docker/src/rabbitmq/utils.go @@ -1298,7 +1298,7 @@ type operationMessage struct { warning bool } -var operationMessageChannel = make(chan operationMessage, 100) +var operationMessageChannel = make(chan operationMessage, 1000) func listenForOperationsMessages() { timer := time.NewTimer(time.Second * 10) @@ -1341,7 +1341,7 @@ func listenForOperationsMessages() { if existingMessage.ID == 0 { err = database.DB.Get(&existingMessage, ` SELECT id, count, "message", source, "level" FROM operationeventlog WHERE - warning=true and source=$1 and operation_id=$2 and resolved=false and deleted=false and "level"=$3 + warning=true and resolved=false and deleted=false and source=$1 and operation_id=$2 and "level"=$3 `, sourceString, operation.ID, msg.messageLevel) if !errors.Is(err, sql.ErrNoRows) && err != nil { logging.LogError(err, "Failed to query existing event log message") @@ -1453,8 +1453,8 @@ func listenForOperationsMessages() { if msg.operationID == 0 { updateObject.OperationID = operation.ID } - if _, err := database.DB.NamedExec(`UPDATE operationeventlog SET - resolved=true WHERE warning=true AND resolved=false AND deleted=false AND message=:message AND operation_id=:operation_id`, updateObject); err != nil { + if _, err := database.DB.NamedExec(`UPDATE operationeventlog SET resolved=true + WHERE warning=true AND resolved=false AND deleted=false AND message=:message AND operation_id=:operation_id`, updateObject); err != nil { logging.LogError(err, "Failed to resolve message") } removeUnresolvedError(operation.ID) @@ -1470,19 +1470,23 @@ func listenForOperationsMessages() { } } func SendAllOperationsMessage(message string, operationID int, source string, messageLevel database.MESSAGE_TYPE, warning bool) { - operationMessageChannel <- operationMessage{ + select { + case operationMessageChannel <- operationMessage{ action: "send", message: message, source: source, operationID: operationID, messageLevel: messageLevel, warning: warning, + }: } } func ResolveAllOperationsMessage(message string, operationID int) { - operationMessageChannel <- operationMessage{ + select { + case operationMessageChannel <- operationMessage{ action: "remove", operationID: operationID, message: message, + }: } }