diff --git a/profiles/webhook_example.yaotl b/profiles/webhook_example.yaotl index b782337a9..cae94c59d 100644 --- a/profiles/webhook_example.yaotl +++ b/profiles/webhook_example.yaotl @@ -14,6 +14,20 @@ WebHook { # optional User = "Havoc" # User name of the webhook bot } + +# +# Mattermost { +# # required +# Url = "..." # replace this with your webhook +# +# # optional +# AvatarUrl = "https://raw.githubusercontent.com/HavocFramework/Havoc/main/assets/Havoc.png" # url to an image to use as an avatar +# +# # optional +# User = "Havoc" # User name of the webhook bot +# } +# + } Operators { diff --git a/teamserver/cmd/server/teamserver.go b/teamserver/cmd/server/teamserver.go index 9d1c21f9b..613cc6a79 100644 --- a/teamserver/cmd/server/teamserver.go +++ b/teamserver/cmd/server/teamserver.go @@ -187,6 +187,23 @@ func (t *Teamserver) Start() { t.WebHooks.SetDiscord(AvatarUrl, UserName, t.Profile.Config.WebHook.Discord.WebHook) } } + + if t.Profile.Config.WebHook.Mattermost != nil { + var ( + AvatarUrl string + UserName string + ) + if len(t.Profile.Config.WebHook.Mattermost.AvatarUrl) > 0 { + AvatarUrl = t.Profile.Config.WebHook.Mattermost.AvatarUrl + } + if len(t.Profile.Config.WebHook.Mattermost.UserName) > 0 { + UserName = t.Profile.Config.WebHook.Mattermost.UserName + } + + if len(t.Profile.Config.WebHook.Mattermost.WebHook) > 0 { + t.WebHooks.SetMattermost(AvatarUrl, UserName, t.Profile.Config.WebHook.Mattermost.WebHook) + } + } } // start teamserver service diff --git a/teamserver/pkg/profile/config.go b/teamserver/pkg/profile/config.go index fe57e2095..6e86fa3a1 100644 --- a/teamserver/pkg/profile/config.go +++ b/teamserver/pkg/profile/config.go @@ -15,8 +15,15 @@ type WebHookDiscordConfig struct { UserName string `yaotl:"User,optional"` } +type WebHookMattermostConfig struct { + WebHook string `yaotl:"Url"` + AvatarUrl string `yaotl:"AvatarUrl,optional"` + UserName string `yaotl:"User,optional"` +} + type WebHookConfig struct { - Discord *WebHookDiscordConfig `yaotl:"Discord,block"` + Discord *WebHookDiscordConfig `yaotl:"Discord,block"` + Mattermost *WebHookMattermostConfig `yaotl:"Mattermost,block"` } type BuildConfig struct { @@ -70,7 +77,7 @@ type ListenerHTTP struct { /* Port used by the agent */ PortConn int `yaotl:"PortConn,optional"` - Methode string `yaotl:"Method,optional"` + Methode string `yaotl:"Method,optional"` /* optional fields */ UserAgent string `yaotl:"UserAgent,optional"` @@ -137,18 +144,18 @@ type ProcessInjectionBlock struct { } type Demon struct { - Sleep int `yaotl:"Sleep,optional"` - Jitter int `yaotl:"Jitter,optional"` - IndirectSyscall bool `yaotl:"IndirectSyscall,optional"` - StackDuplication bool `yaotl:"StackDuplication,optional"` - SleepTechnique string `yaotl:"SleepTechnique,optional"` - ProxyLoading string `yaotl:"ProxyLoading,optional"` - AmsiEtwPatching string `yaotl:"AmsiEtwPatching,optional"` - ProcessInjection *ProcessInjectionBlock `yaotl:"Injection,block"` + Sleep int `yaotl:"Sleep,optional"` + Jitter int `yaotl:"Jitter,optional"` + IndirectSyscall bool `yaotl:"IndirectSyscall,optional"` + StackDuplication bool `yaotl:"StackDuplication,optional"` + SleepTechnique string `yaotl:"SleepTechnique,optional"` + ProxyLoading string `yaotl:"ProxyLoading,optional"` + AmsiEtwPatching string `yaotl:"AmsiEtwPatching,optional"` + ProcessInjection *ProcessInjectionBlock `yaotl:"Injection,block"` - DotNetNamePipe string `yaotl:"DotNetNamePipe,optional"` + DotNetNamePipe string `yaotl:"DotNetNamePipe,optional"` - Binary *Binary `yaotl:"Binary,block"` + Binary *Binary `yaotl:"Binary,block"` - TrustXForwardedFor bool `yaotl:"TrustXForwardedFor,optional"` + TrustXForwardedFor bool `yaotl:"TrustXForwardedFor,optional"` } diff --git a/teamserver/pkg/webhook/mattermost.go b/teamserver/pkg/webhook/mattermost.go new file mode 100644 index 000000000..402dbf6dc --- /dev/null +++ b/teamserver/pkg/webhook/mattermost.go @@ -0,0 +1,7 @@ +package webhook + +type Mattermost_Message struct { + AvatarUrl *string `json:"icon_url,omitempty"` + Content *string `json:"text,omitempty"` + Username *string `json:"username,omitempty"` +} diff --git a/teamserver/pkg/webhook/webhook.go b/teamserver/pkg/webhook/webhook.go index 873c30367..412cd1838 100644 --- a/teamserver/pkg/webhook/webhook.go +++ b/teamserver/pkg/webhook/webhook.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "strconv" + "strings" ) type WebHook struct { @@ -15,6 +16,12 @@ type WebHook struct { User string Url string } + + Mattermost struct { + Avatar string + User string + Url string + } } func StringPtr(str string) *string { @@ -127,6 +134,76 @@ func (w *WebHook) NewAgent(agent map[string]any) error { return nil } + if len(w.Mattermost.Url) > 0 { + var ( + payload = new(bytes.Buffer) + message Mattermost_Message + tableRows []string + AgentInfo map[string]any + ) + + AgentInfo, ok := agent["Info"].(map[string]interface{}) + if !ok { + return fmt.Errorf("unable to assert AgentInfo as map[string]interface{}") + } + + tableRows = append(tableRows, "| Field | Value |") + tableRows = append(tableRows, "| ----- | ----- |") + + addRow := func(name, value string) { + tableRows = append(tableRows, fmt.Sprintf("| %s | %s |", name, value)) + } + + addRow("Agent ID", agent["NameID"].(string)) + addRow("Username", AgentInfo["Username"].(string)) + addRow("Hostname", AgentInfo["Hostname"].(string)) + addRow("Domain", AgentInfo["DomainName"].(string)) + addRow("Internal IP", AgentInfo["InternalIP"].(string)) + addRow("Process Path", AgentInfo["ProcessPath"].(string)) + addRow("Process Name", AgentInfo["ProcessName"].(string)) + addRow("Process ID", strconv.Itoa(AgentInfo["ProcessPID"].(int))) + addRow("Process Arch", AgentInfo["ProcessArch"].(string)) + addRow("OS Version", AgentInfo["OSVersion"].(string)) + addRow("OS Arch", AgentInfo["OSArch"].(string)) + addRow("First Callback", AgentInfo["FirstCallIn"].(string)) + addRow("Elevated", AgentInfo["Elevated"].(string)) + addRow("SleepDelay", strconv.Itoa(AgentInfo["SleepDelay"].(int))) + addRow("SleepJitter", strconv.Itoa(AgentInfo["SleepJitter"].(int))) + + content := fmt.Sprintf("**New Havoc Callback!**\n\n%s", strings.Join(tableRows, "\n")) + message.Content = &content + + if len(w.Mattermost.Avatar) > 0 { + message.AvatarUrl = &w.Mattermost.Avatar + } + + if len(w.Mattermost.User) > 0 { + message.Username = &w.Mattermost.User + } + + err := json.NewEncoder(payload).Encode(message) + if err != nil { + return err + } + + resp, err := http.Post(w.Mattermost.Url, "application/json", payload) + if err != nil { + return err + } + + if resp.StatusCode != 200 && resp.StatusCode != 204 { + defer resp.Body.Close() + + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + return fmt.Errorf(string(responseBody)) + } + + return nil + } return nil } @@ -136,3 +213,9 @@ func (w *WebHook) SetDiscord(AvatarUrl, User, Url string) { w.Discord.User = User w.Discord.Url = Url } + +func (w *WebHook) SetMattermost(AvatarUrl, User, Url string) { + w.Mattermost.Avatar = AvatarUrl + w.Mattermost.User = User + w.Mattermost.Url = Url +}