mirror of
https://github.com/mariocandela/beelzebub.git
synced 2025-07-01 18:47:26 -04:00
Feat: Add FallbackCommand for HTTP Strategy, refactor packages strategies (#175)
Add FallbackCommand for HTTP Strategy, refactor packages strategies, improve histories implementations.
This commit is contained in:
@ -3,15 +3,18 @@ package builder
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mariocandela/beelzebub/v3/parser"
|
|
||||||
"github.com/mariocandela/beelzebub/v3/plugins"
|
|
||||||
"github.com/mariocandela/beelzebub/v3/protocols"
|
|
||||||
"github.com/mariocandela/beelzebub/v3/protocols/strategies"
|
|
||||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/mariocandela/beelzebub/v3/parser"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/plugins"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/protocols"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/protocols/strategies/HTTP"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/protocols/strategies/SSH"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/protocols/strategies/TCP"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
amqp "github.com/rabbitmq/amqp091-go"
|
amqp "github.com/rabbitmq/amqp091-go"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -106,9 +109,9 @@ Honeypot Framework, happy hacking!`)
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Init Protocol strategies
|
// Init Protocol strategies
|
||||||
secureShellStrategy := &strategies.SSHStrategy{}
|
secureShellStrategy := &SSH.SSHStrategy{}
|
||||||
hypertextTransferProtocolStrategy := &strategies.HTTPStrategy{}
|
hypertextTransferProtocolStrategy := &HTTP.HTTPStrategy{}
|
||||||
transmissionControlProtocolStrategy := &strategies.TCPStrategy{}
|
transmissionControlProtocolStrategy := &TCP.TCPStrategy{}
|
||||||
|
|
||||||
// Init Tracer strategies, and set the trace strategy default HTTP
|
// Init Tracer strategies, and set the trace strategy default HTTP
|
||||||
protocolManager := protocols.InitProtocolManager(b.traceStrategy, hypertextTransferProtocolStrategy)
|
protocolManager := protocols.InitProtocolManager(b.traceStrategy, hypertextTransferProtocolStrategy)
|
||||||
@ -122,7 +125,7 @@ Honeypot Framework, happy hacking!`)
|
|||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
if len(honeypotsConfiguration) == 0 {
|
if len(honeypotsConfiguration) == 0 {
|
||||||
return errors.New("No honeypots configuration found")
|
return errors.New("no honeypots configuration found")
|
||||||
}
|
}
|
||||||
b.beelzebubServicesConfiguration = honeypotsConfiguration
|
b.beelzebubServicesConfiguration = honeypotsConfiguration
|
||||||
}
|
}
|
||||||
@ -132,20 +135,16 @@ Honeypot Framework, happy hacking!`)
|
|||||||
switch beelzebubServiceConfiguration.Protocol {
|
switch beelzebubServiceConfiguration.Protocol {
|
||||||
case "http":
|
case "http":
|
||||||
protocolManager.SetProtocolStrategy(hypertextTransferProtocolStrategy)
|
protocolManager.SetProtocolStrategy(hypertextTransferProtocolStrategy)
|
||||||
break
|
|
||||||
case "ssh":
|
case "ssh":
|
||||||
protocolManager.SetProtocolStrategy(secureShellStrategy)
|
protocolManager.SetProtocolStrategy(secureShellStrategy)
|
||||||
break
|
|
||||||
case "tcp":
|
case "tcp":
|
||||||
protocolManager.SetProtocolStrategy(transmissionControlProtocolStrategy)
|
protocolManager.SetProtocolStrategy(transmissionControlProtocolStrategy)
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Protocol %s not managed", beelzebubServiceConfiguration.Protocol)
|
log.Fatalf("protocol %s not managed", beelzebubServiceConfiguration.Protocol)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := protocolManager.InitService(beelzebubServiceConfiguration); err != nil {
|
if err := protocolManager.InitService(beelzebubServiceConfiguration); err != nil {
|
||||||
return errors.New(fmt.Sprintf("Error during init protocol: %s, %s", beelzebubServiceConfiguration.Protocol, err.Error()))
|
return fmt.Errorf("error during init protocol: %s, %s", beelzebubServiceConfiguration.Protocol, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
go.mod
2
go.mod
@ -13,6 +13,7 @@ require (
|
|||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
golang.org/x/crypto v0.33.0
|
golang.org/x/crypto v0.33.0
|
||||||
|
golang.org/x/term v0.29.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,6 +34,5 @@ require (
|
|||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.33.0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/term v0.29.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
)
|
)
|
||||||
|
47
historystore/history_store.go
Normal file
47
historystore/history_store.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package historystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/mariocandela/beelzebub/v3/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HistoryStore is a thread-safe structure for storing Messages used to build LLM Context.
|
||||||
|
type HistoryStore struct {
|
||||||
|
sync.RWMutex
|
||||||
|
sessions map[string][]plugins.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHistoryStore returns a prepared HistoryStore
|
||||||
|
func NewHistoryStore() *HistoryStore {
|
||||||
|
return &HistoryStore{
|
||||||
|
sessions: make(map[string][]plugins.Message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasKey returns true if the supplied key exists in the map.
|
||||||
|
func (hs *HistoryStore) HasKey(key string) bool {
|
||||||
|
hs.RLock()
|
||||||
|
defer hs.RUnlock()
|
||||||
|
_, ok := hs.sessions[key]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query returns the value stored at the map
|
||||||
|
func (hs *HistoryStore) Query(key string) []plugins.Message {
|
||||||
|
hs.RLock()
|
||||||
|
defer hs.RUnlock()
|
||||||
|
return hs.sessions[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append will add the slice of Mesages to the entry for the key.
|
||||||
|
// If the map has not yet been initalised, then a new map is created.
|
||||||
|
func (hs *HistoryStore) Append(key string, message ...plugins.Message) {
|
||||||
|
hs.Lock()
|
||||||
|
defer hs.Unlock()
|
||||||
|
// In the unexpected case that the map has not yet been initalised, create it.
|
||||||
|
if hs.sessions == nil {
|
||||||
|
hs.sessions = make(map[string][]plugins.Message)
|
||||||
|
}
|
||||||
|
hs.sessions[key] = append(hs.sessions[key], message...)
|
||||||
|
}
|
47
historystore/history_store_test.go
Normal file
47
historystore/history_store_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package historystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mariocandela/beelzebub/v3/plugins"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewHistoryStore(t *testing.T) {
|
||||||
|
hs := NewHistoryStore()
|
||||||
|
assert.NotNil(t, hs)
|
||||||
|
assert.NotNil(t, hs.sessions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasKey(t *testing.T) {
|
||||||
|
hs := NewHistoryStore()
|
||||||
|
hs.sessions["testKey"] = []plugins.Message{}
|
||||||
|
assert.True(t, hs.HasKey("testKey"))
|
||||||
|
assert.False(t, hs.HasKey("nonExistentKey"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuery(t *testing.T) {
|
||||||
|
hs := NewHistoryStore()
|
||||||
|
expectedMessages := []plugins.Message{{Role: "user", Content: "Hello"}}
|
||||||
|
hs.sessions["testKey"] = expectedMessages
|
||||||
|
actualMessages := hs.Query("testKey")
|
||||||
|
assert.Equal(t, expectedMessages, actualMessages)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppend(t *testing.T) {
|
||||||
|
hs := NewHistoryStore()
|
||||||
|
message1 := plugins.Message{Role: "user", Content: "Hello"}
|
||||||
|
message2 := plugins.Message{Role: "assistant", Content: "Hi"}
|
||||||
|
hs.Append("testKey", message1)
|
||||||
|
assert.Equal(t, []plugins.Message{message1}, hs.sessions["testKey"])
|
||||||
|
hs.Append("testKey", message2)
|
||||||
|
assert.Equal(t, []plugins.Message{message1, message2}, hs.sessions["testKey"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendNilSessions(t *testing.T) {
|
||||||
|
hs := &HistoryStore{}
|
||||||
|
message1 := plugins.Message{Role: "user", Content: "Hello"}
|
||||||
|
hs.Append("testKey", message1)
|
||||||
|
assert.NotNil(t, hs.sessions)
|
||||||
|
assert.Equal(t, []plugins.Message{message1}, hs.sessions["testKey"])
|
||||||
|
}
|
@ -62,6 +62,7 @@ type BeelzebubServiceConfiguration struct {
|
|||||||
Protocol string `yaml:"protocol"`
|
Protocol string `yaml:"protocol"`
|
||||||
Address string `yaml:"address"`
|
Address string `yaml:"address"`
|
||||||
Commands []Command `yaml:"commands"`
|
Commands []Command `yaml:"commands"`
|
||||||
|
FallbackCommand Command `yaml:"fallbackCommand"`
|
||||||
ServerVersion string `yaml:"serverVersion"`
|
ServerVersion string `yaml:"serverVersion"`
|
||||||
ServerName string `yaml:"serverName"`
|
ServerName string `yaml:"serverName"`
|
||||||
DeadlineTimeoutSeconds int `yaml:"deadlineTimeoutSeconds"`
|
DeadlineTimeoutSeconds int `yaml:"deadlineTimeoutSeconds"`
|
||||||
|
@ -56,6 +56,9 @@ commands:
|
|||||||
handler: "login"
|
handler: "login"
|
||||||
headers:
|
headers:
|
||||||
- "Content-Type: text/html"
|
- "Content-Type: text/html"
|
||||||
|
fallbackCommand:
|
||||||
|
handler: "404 Not Found!"
|
||||||
|
statusCode: 404
|
||||||
plugin:
|
plugin:
|
||||||
openAISecretKey: "qwerty"
|
openAISecretKey: "qwerty"
|
||||||
llmModel: "llama3"
|
llmModel: "llama3"
|
||||||
@ -134,6 +137,8 @@ func TestReadConfigurationsServicesValid(t *testing.T) {
|
|||||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Commands[0].Handler, "login")
|
assert.Equal(t, firstBeelzebubServiceConfiguration.Commands[0].Handler, "login")
|
||||||
assert.Equal(t, len(firstBeelzebubServiceConfiguration.Commands[0].Headers), 1)
|
assert.Equal(t, len(firstBeelzebubServiceConfiguration.Commands[0].Headers), 1)
|
||||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Commands[0].Headers[0], "Content-Type: text/html")
|
assert.Equal(t, firstBeelzebubServiceConfiguration.Commands[0].Headers[0], "Content-Type: text/html")
|
||||||
|
assert.Equal(t, firstBeelzebubServiceConfiguration.FallbackCommand.Handler, "404 Not Found!")
|
||||||
|
assert.Equal(t, firstBeelzebubServiceConfiguration.FallbackCommand.StatusCode, 404)
|
||||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Plugin.OpenAISecretKey, "qwerty")
|
assert.Equal(t, firstBeelzebubServiceConfiguration.Plugin.OpenAISecretKey, "qwerty")
|
||||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Plugin.LLMModel, "llama3")
|
assert.Equal(t, firstBeelzebubServiceConfiguration.Plugin.LLMModel, "llama3")
|
||||||
assert.Equal(t, firstBeelzebubServiceConfiguration.Plugin.LLMProvider, "ollama")
|
assert.Equal(t, firstBeelzebubServiceConfiguration.Plugin.LLMProvider, "ollama")
|
||||||
@ -176,7 +181,8 @@ func TestGelAllFilesNameByDirNameError(t *testing.T) {
|
|||||||
files, err := gelAllFilesNameByDirName("nosuchfile")
|
files, err := gelAllFilesNameByDirName("nosuchfile")
|
||||||
|
|
||||||
assert.Nil(t, files)
|
assert.Nil(t, files)
|
||||||
assert.Equal(t, "open nosuchfile: no such file or directory", err.Error())
|
// Windows and Linux return slightly different error strings, but share a common prefix, so check for that.
|
||||||
|
assert.Contains(t, err.Error(), "open nosuchfile: ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadFileBytesByFilePath(t *testing.T) {
|
func TestReadFileBytesByFilePath(t *testing.T) {
|
||||||
|
186
protocols/strategies/HTTP/http.go
Normal file
186
protocols/strategies/HTTP/http.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package HTTP
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mariocandela/beelzebub/v3/parser"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/plugins"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPStrategy struct{}
|
||||||
|
|
||||||
|
type httpResponse struct {
|
||||||
|
StatusCode int
|
||||||
|
Headers []string
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (httpStrategy HTTPStrategy) Init(servConf parser.BeelzebubServiceConfiguration, tr tracer.Tracer) error {
|
||||||
|
serverMux := http.NewServeMux()
|
||||||
|
|
||||||
|
serverMux.HandleFunc("/", func(responseWriter http.ResponseWriter, request *http.Request) {
|
||||||
|
traceRequest(request, tr, servConf.Description)
|
||||||
|
var matched bool
|
||||||
|
var resp httpResponse
|
||||||
|
var err error
|
||||||
|
for _, command := range servConf.Commands {
|
||||||
|
var err error
|
||||||
|
matched, err = regexp.MatchString(command.Regex, request.RequestURI)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error parsing regex: %s, %s", command.Regex, err.Error())
|
||||||
|
resp.StatusCode = 500
|
||||||
|
resp.Body = "500 Internal Server Error"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if matched {
|
||||||
|
resp, err = buildHTTPResponse(servConf, command, request)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error building http response: %s: %v", request.RequestURI, err)
|
||||||
|
resp.StatusCode = 500
|
||||||
|
resp.Body = "500 Internal Server Error"
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If none of the main commands matched, and we have a fallback command configured, process it here.
|
||||||
|
// The regexp is ignored for fallback commands, as they are catch-all for any request.
|
||||||
|
if !matched {
|
||||||
|
command := servConf.FallbackCommand
|
||||||
|
if command.Handler != "" || command.Plugin != "" {
|
||||||
|
resp, err = buildHTTPResponse(servConf, command, request)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error building http response: %s: %v", request.RequestURI, err)
|
||||||
|
resp.StatusCode = 500
|
||||||
|
resp.Body = "500 Internal Server Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setResponseHeaders(responseWriter, resp.Headers, resp.StatusCode)
|
||||||
|
fmt.Fprint(responseWriter, resp.Body)
|
||||||
|
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
// Launch a TLS supporting server if we are supplied a TLS Key and Certificate.
|
||||||
|
// If relative paths are supplied, they are relative to the CWD of the binary.
|
||||||
|
// The can be self-signed, only the client will validate this (or not).
|
||||||
|
if servConf.TLSKeyPath != "" && servConf.TLSCertPath != "" {
|
||||||
|
err = http.ListenAndServeTLS(servConf.Address, servConf.TLSCertPath, servConf.TLSKeyPath, serverMux)
|
||||||
|
} else {
|
||||||
|
err = http.ListenAndServe(servConf.Address, serverMux)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error during init HTTP Protocol: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"port": servConf.Address,
|
||||||
|
"commands": len(servConf.Commands),
|
||||||
|
}).Infof("Init service: %s", servConf.Description)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildHTTPResponse(servConf parser.BeelzebubServiceConfiguration, command parser.Command, request *http.Request) (httpResponse, error) {
|
||||||
|
resp := httpResponse{
|
||||||
|
Body: command.Handler,
|
||||||
|
Headers: command.Headers,
|
||||||
|
StatusCode: command.StatusCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if command.Plugin == plugins.LLMPluginName {
|
||||||
|
llmProvider, err := plugins.FromStringToLLMProvider(servConf.Plugin.LLMProvider)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error: %v", err)
|
||||||
|
resp.Body = "404 Not Found!"
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
llmHoneypot := plugins.LLMHoneypot{
|
||||||
|
Histories: make([]plugins.Message, 0),
|
||||||
|
OpenAIKey: servConf.Plugin.OpenAISecretKey,
|
||||||
|
Protocol: tracer.HTTP,
|
||||||
|
Host: servConf.Plugin.Host,
|
||||||
|
Model: servConf.Plugin.LLMModel,
|
||||||
|
Provider: llmProvider,
|
||||||
|
CustomPrompt: servConf.Plugin.Prompt,
|
||||||
|
}
|
||||||
|
llmHoneypotInstance := plugins.InitLLMHoneypot(llmHoneypot)
|
||||||
|
command := fmt.Sprintf("%s %s", request.Method, request.RequestURI)
|
||||||
|
|
||||||
|
completions, err := llmHoneypotInstance.ExecuteModel(command)
|
||||||
|
if err != nil {
|
||||||
|
resp.Body = "404 Not Found!"
|
||||||
|
return resp, fmt.Errorf("ExecuteModel error: %s, %v", command, err)
|
||||||
|
}
|
||||||
|
resp.Body = completions
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func traceRequest(request *http.Request, tr tracer.Tracer, HoneypotDescription string) {
|
||||||
|
bodyBytes, err := io.ReadAll(request.Body)
|
||||||
|
body := ""
|
||||||
|
if err == nil {
|
||||||
|
body = string(bodyBytes)
|
||||||
|
}
|
||||||
|
host, port, _ := net.SplitHostPort(request.RemoteAddr)
|
||||||
|
|
||||||
|
event := tracer.Event{
|
||||||
|
Msg: "HTTP New request",
|
||||||
|
RequestURI: request.RequestURI,
|
||||||
|
Protocol: tracer.HTTP.String(),
|
||||||
|
HTTPMethod: request.Method,
|
||||||
|
Body: body,
|
||||||
|
HostHTTPRequest: request.Host,
|
||||||
|
UserAgent: request.UserAgent(),
|
||||||
|
Cookies: mapCookiesToString(request.Cookies()),
|
||||||
|
Headers: request.Header,
|
||||||
|
Status: tracer.Stateless.String(),
|
||||||
|
RemoteAddr: request.RemoteAddr,
|
||||||
|
SourceIp: host,
|
||||||
|
SourcePort: port,
|
||||||
|
ID: uuid.New().String(),
|
||||||
|
Description: HoneypotDescription,
|
||||||
|
}
|
||||||
|
// Capture the TLS details from the request, if provided.
|
||||||
|
if request.TLS != nil {
|
||||||
|
event.Msg = "HTTPS New Request"
|
||||||
|
event.TLSServerName = request.TLS.ServerName
|
||||||
|
}
|
||||||
|
tr.TraceEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapCookiesToString(cookies []*http.Cookie) string {
|
||||||
|
cookiesString := ""
|
||||||
|
|
||||||
|
for _, cookie := range cookies {
|
||||||
|
cookiesString += cookie.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookiesString
|
||||||
|
}
|
||||||
|
|
||||||
|
func setResponseHeaders(responseWriter http.ResponseWriter, headers []string, statusCode int) {
|
||||||
|
for _, headerStr := range headers {
|
||||||
|
keyValue := strings.Split(headerStr, ":")
|
||||||
|
if len(keyValue) > 1 {
|
||||||
|
responseWriter.Header().Add(keyValue[0], keyValue[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// http.StatusText(statusCode): empty string if the code is unknown.
|
||||||
|
if len(http.StatusText(statusCode)) > 0 {
|
||||||
|
responseWriter.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,17 @@
|
|||||||
package strategies
|
package SSH
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mariocandela/beelzebub/v3/parser"
|
|
||||||
"github.com/mariocandela/beelzebub/v3/plugins"
|
|
||||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mariocandela/beelzebub/v3/historystore"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/parser"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/plugins"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||||
|
|
||||||
"github.com/gliderlabs/ssh"
|
"github.com/gliderlabs/ssh"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -17,65 +19,60 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SSHStrategy struct {
|
type SSHStrategy struct {
|
||||||
Sessions map[string][]plugins.Message
|
Sessions *historystore.HistoryStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.BeelzebubServiceConfiguration, tr tracer.Tracer) error {
|
func (sshStrategy *SSHStrategy) Init(servConf parser.BeelzebubServiceConfiguration, tr tracer.Tracer) error {
|
||||||
sshStrategy.Sessions = make(map[string][]plugins.Message)
|
if sshStrategy.Sessions == nil {
|
||||||
|
sshStrategy.Sessions = historystore.NewHistoryStore()
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
server := &ssh.Server{
|
server := &ssh.Server{
|
||||||
Addr: beelzebubServiceConfiguration.Address,
|
Addr: servConf.Address,
|
||||||
MaxTimeout: time.Duration(beelzebubServiceConfiguration.DeadlineTimeoutSeconds) * time.Second,
|
MaxTimeout: time.Duration(servConf.DeadlineTimeoutSeconds) * time.Second,
|
||||||
IdleTimeout: time.Duration(beelzebubServiceConfiguration.DeadlineTimeoutSeconds) * time.Second,
|
IdleTimeout: time.Duration(servConf.DeadlineTimeoutSeconds) * time.Second,
|
||||||
Version: beelzebubServiceConfiguration.ServerVersion,
|
Version: servConf.ServerVersion,
|
||||||
Handler: func(sess ssh.Session) {
|
Handler: func(sess ssh.Session) {
|
||||||
uuidSession := uuid.New()
|
uuidSession := uuid.New()
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(sess.RemoteAddr().String())
|
host, port, _ := net.SplitHostPort(sess.RemoteAddr().String())
|
||||||
sessionKey := host + sess.User()
|
sessionKey := "SSH" + host + sess.User()
|
||||||
|
|
||||||
// Inline SSH command
|
// Inline SSH command
|
||||||
if sess.RawCommand() != "" {
|
if sess.RawCommand() != "" {
|
||||||
for _, command := range beelzebubServiceConfiguration.Commands {
|
for _, command := range servConf.Commands {
|
||||||
matched, err := regexp.MatchString(command.Regex, sess.RawCommand())
|
matched, err := regexp.MatchString(command.Regex, sess.RawCommand())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error regex: %s, %s", command.Regex, err.Error())
|
log.Errorf("error regex: %s, %s", command.Regex, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if matched {
|
if matched {
|
||||||
commandOutput := command.Handler
|
commandOutput := command.Handler
|
||||||
|
|
||||||
if command.Plugin == plugins.LLMPluginName {
|
if command.Plugin == plugins.LLMPluginName {
|
||||||
|
llmProvider, err := plugins.FromStringToLLMProvider(servConf.Plugin.LLMProvider)
|
||||||
llmProvider, err := plugins.FromStringToLLMProvider(beelzebubServiceConfiguration.Plugin.LLMProvider)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error: %s", err.Error())
|
log.Errorf("error: %s", err.Error())
|
||||||
commandOutput = "command not found"
|
commandOutput = "command not found"
|
||||||
llmProvider = plugins.OpenAI
|
llmProvider = plugins.OpenAI
|
||||||
}
|
}
|
||||||
|
|
||||||
histories := make([]plugins.Message, 0)
|
var histories []plugins.Message
|
||||||
|
if sshStrategy.Sessions.HasKey(sessionKey) {
|
||||||
if sshStrategy.Sessions[sessionKey] != nil {
|
histories = sshStrategy.Sessions.Query(sessionKey)
|
||||||
histories = sshStrategy.Sessions[sessionKey]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
llmHoneypot := plugins.LLMHoneypot{
|
llmHoneypot := plugins.LLMHoneypot{
|
||||||
Histories: histories,
|
Histories: histories,
|
||||||
OpenAIKey: beelzebubServiceConfiguration.Plugin.OpenAISecretKey,
|
OpenAIKey: servConf.Plugin.OpenAISecretKey,
|
||||||
Protocol: tracer.SSH,
|
Protocol: tracer.SSH,
|
||||||
Host: beelzebubServiceConfiguration.Plugin.Host,
|
Host: servConf.Plugin.Host,
|
||||||
Model: beelzebubServiceConfiguration.Plugin.LLMModel,
|
Model: servConf.Plugin.LLMModel,
|
||||||
Provider: llmProvider,
|
Provider: llmProvider,
|
||||||
CustomPrompt: beelzebubServiceConfiguration.Plugin.Prompt,
|
CustomPrompt: servConf.Plugin.Prompt,
|
||||||
}
|
}
|
||||||
|
|
||||||
llmHoneypotInstance := plugins.InitLLMHoneypot(llmHoneypot)
|
llmHoneypotInstance := plugins.InitLLMHoneypot(llmHoneypot)
|
||||||
|
|
||||||
if commandOutput, err = llmHoneypotInstance.ExecuteModel(sess.RawCommand()); err != nil {
|
if commandOutput, err = llmHoneypotInstance.ExecuteModel(sess.RawCommand()); err != nil {
|
||||||
log.Errorf("Error ExecuteModel: %s, %s", sess.RawCommand(), err.Error())
|
log.Errorf("error ExecuteModel: %s, %s", sess.RawCommand(), err.Error())
|
||||||
commandOutput = "command not found"
|
commandOutput = "command not found"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +80,7 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
sess.Write(append([]byte(commandOutput), '\n'))
|
sess.Write(append([]byte(commandOutput), '\n'))
|
||||||
|
|
||||||
tr.TraceEvent(tracer.Event{
|
tr.TraceEvent(tracer.Event{
|
||||||
Msg: "New SSH Session",
|
Msg: "New SSH Raw Command Session",
|
||||||
Protocol: tracer.SSH.String(),
|
Protocol: tracer.SSH.String(),
|
||||||
RemoteAddr: sess.RemoteAddr().String(),
|
RemoteAddr: sess.RemoteAddr().String(),
|
||||||
SourceIp: host,
|
SourceIp: host,
|
||||||
@ -92,16 +89,20 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
ID: uuidSession.String(),
|
ID: uuidSession.String(),
|
||||||
Environ: strings.Join(sess.Environ(), ","),
|
Environ: strings.Join(sess.Environ(), ","),
|
||||||
User: sess.User(),
|
User: sess.User(),
|
||||||
Description: beelzebubServiceConfiguration.Description,
|
Description: servConf.Description,
|
||||||
Command: sess.RawCommand(),
|
Command: sess.RawCommand(),
|
||||||
CommandOutput: commandOutput,
|
CommandOutput: commandOutput,
|
||||||
})
|
})
|
||||||
|
|
||||||
var histories []plugins.Message
|
var histories []plugins.Message
|
||||||
|
if sshStrategy.Sessions.HasKey(sessionKey) {
|
||||||
|
histories = sshStrategy.Sessions.Query(sessionKey)
|
||||||
|
}
|
||||||
histories = append(histories, plugins.Message{Role: plugins.USER.String(), Content: sess.RawCommand()})
|
histories = append(histories, plugins.Message{Role: plugins.USER.String(), Content: sess.RawCommand()})
|
||||||
histories = append(histories, plugins.Message{Role: plugins.ASSISTANT.String(), Content: commandOutput})
|
histories = append(histories, plugins.Message{Role: plugins.ASSISTANT.String(), Content: commandOutput})
|
||||||
sshStrategy.Sessions[sessionKey] = histories
|
sshStrategy.Sessions.Append(sessionKey, histories...)
|
||||||
tr.TraceEvent(tracer.Event{
|
tr.TraceEvent(tracer.Event{
|
||||||
Msg: "End SSH Session",
|
Msg: "End SSH Raw Command Session",
|
||||||
Status: tracer.End.String(),
|
Status: tracer.End.String(),
|
||||||
ID: uuidSession.String(),
|
ID: uuidSession.String(),
|
||||||
})
|
})
|
||||||
@ -111,7 +112,7 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr.TraceEvent(tracer.Event{
|
tr.TraceEvent(tracer.Event{
|
||||||
Msg: "New SSH Session",
|
Msg: "New SSH Terminal Session",
|
||||||
Protocol: tracer.SSH.String(),
|
Protocol: tracer.SSH.String(),
|
||||||
RemoteAddr: sess.RemoteAddr().String(),
|
RemoteAddr: sess.RemoteAddr().String(),
|
||||||
SourceIp: host,
|
SourceIp: host,
|
||||||
@ -120,13 +121,13 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
ID: uuidSession.String(),
|
ID: uuidSession.String(),
|
||||||
Environ: strings.Join(sess.Environ(), ","),
|
Environ: strings.Join(sess.Environ(), ","),
|
||||||
User: sess.User(),
|
User: sess.User(),
|
||||||
Description: beelzebubServiceConfiguration.Description,
|
Description: servConf.Description,
|
||||||
})
|
})
|
||||||
|
|
||||||
terminal := term.NewTerminal(sess, buildPrompt(sess.User(), beelzebubServiceConfiguration.ServerName))
|
terminal := term.NewTerminal(sess, buildPrompt(sess.User(), servConf.ServerName))
|
||||||
var histories []plugins.Message
|
var histories []plugins.Message
|
||||||
if sshStrategy.Sessions[sessionKey] != nil {
|
if sshStrategy.Sessions.HasKey(sessionKey) {
|
||||||
histories = sshStrategy.Sessions[sessionKey]
|
histories = sshStrategy.Sessions.Query(sessionKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -134,43 +135,36 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if commandInput == "exit" {
|
if commandInput == "exit" {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
for _, command := range beelzebubServiceConfiguration.Commands {
|
for _, command := range servConf.Commands {
|
||||||
matched, err := regexp.MatchString(command.Regex, commandInput)
|
matched, err := regexp.MatchString(command.Regex, commandInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error regex: %s, %s", command.Regex, err.Error())
|
log.Errorf("error regex: %s, %s", command.Regex, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if matched {
|
if matched {
|
||||||
commandOutput := command.Handler
|
commandOutput := command.Handler
|
||||||
|
|
||||||
if command.Plugin == plugins.LLMPluginName {
|
if command.Plugin == plugins.LLMPluginName {
|
||||||
|
llmProvider, err := plugins.FromStringToLLMProvider(servConf.Plugin.LLMProvider)
|
||||||
llmProvider, err := plugins.FromStringToLLMProvider(beelzebubServiceConfiguration.Plugin.LLMProvider)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error: %s, fallback OpenAI", err.Error())
|
log.Errorf("error: %s, fallback OpenAI", err.Error())
|
||||||
llmProvider = plugins.OpenAI
|
llmProvider = plugins.OpenAI
|
||||||
}
|
}
|
||||||
|
|
||||||
llmHoneypot := plugins.LLMHoneypot{
|
llmHoneypot := plugins.LLMHoneypot{
|
||||||
Histories: histories,
|
Histories: histories,
|
||||||
OpenAIKey: beelzebubServiceConfiguration.Plugin.OpenAISecretKey,
|
OpenAIKey: servConf.Plugin.OpenAISecretKey,
|
||||||
Protocol: tracer.SSH,
|
Protocol: tracer.SSH,
|
||||||
Host: beelzebubServiceConfiguration.Plugin.Host,
|
Host: servConf.Plugin.Host,
|
||||||
Model: beelzebubServiceConfiguration.Plugin.LLMModel,
|
Model: servConf.Plugin.LLMModel,
|
||||||
Provider: llmProvider,
|
Provider: llmProvider,
|
||||||
CustomPrompt: beelzebubServiceConfiguration.Plugin.Prompt,
|
CustomPrompt: servConf.Plugin.Prompt,
|
||||||
}
|
}
|
||||||
|
|
||||||
llmHoneypotInstance := plugins.InitLLMHoneypot(llmHoneypot)
|
llmHoneypotInstance := plugins.InitLLMHoneypot(llmHoneypot)
|
||||||
|
|
||||||
if commandOutput, err = llmHoneypotInstance.ExecuteModel(commandInput); err != nil {
|
if commandOutput, err = llmHoneypotInstance.ExecuteModel(commandInput); err != nil {
|
||||||
log.Errorf("Error ExecuteModel: %s, %s", commandInput, err.Error())
|
log.Errorf("error ExecuteModel: %s, %s", commandInput, err.Error())
|
||||||
commandOutput = "command not found"
|
commandOutput = "command not found"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,13 +184,16 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
CommandOutput: commandOutput,
|
CommandOutput: commandOutput,
|
||||||
ID: uuidSession.String(),
|
ID: uuidSession.String(),
|
||||||
Protocol: tracer.SSH.String(),
|
Protocol: tracer.SSH.String(),
|
||||||
Description: beelzebubServiceConfiguration.Description,
|
Description: servConf.Description,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sshStrategy.Sessions[sessionKey] = histories
|
|
||||||
|
// Add all history events for the terminal session to the store.
|
||||||
|
// This is done at the end of the session to avoid excess lock operations.
|
||||||
|
sshStrategy.Sessions.Append(sessionKey, histories...)
|
||||||
tr.TraceEvent(tracer.Event{
|
tr.TraceEvent(tracer.Event{
|
||||||
Msg: "End SSH Session",
|
Msg: "End SSH Session",
|
||||||
Status: tracer.End.String(),
|
Status: tracer.End.String(),
|
||||||
@ -207,7 +204,7 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
host, port, _ := net.SplitHostPort(ctx.RemoteAddr().String())
|
host, port, _ := net.SplitHostPort(ctx.RemoteAddr().String())
|
||||||
|
|
||||||
tr.TraceEvent(tracer.Event{
|
tr.TraceEvent(tracer.Event{
|
||||||
Msg: "New SSH attempt",
|
Msg: "New SSH Login Attempt",
|
||||||
Protocol: tracer.SSH.String(),
|
Protocol: tracer.SSH.String(),
|
||||||
Status: tracer.Stateless.String(),
|
Status: tracer.Stateless.String(),
|
||||||
User: ctx.User(),
|
User: ctx.User(),
|
||||||
@ -217,11 +214,11 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
SourceIp: host,
|
SourceIp: host,
|
||||||
SourcePort: port,
|
SourcePort: port,
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
Description: beelzebubServiceConfiguration.Description,
|
Description: servConf.Description,
|
||||||
})
|
})
|
||||||
matched, err := regexp.MatchString(beelzebubServiceConfiguration.PasswordRegex, password)
|
matched, err := regexp.MatchString(servConf.PasswordRegex, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error regex: %s, %s", beelzebubServiceConfiguration.PasswordRegex, err.Error())
|
log.Errorf("error regex: %s, %s", servConf.PasswordRegex, err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return matched
|
return matched
|
||||||
@ -229,14 +226,14 @@ func (sshStrategy *SSHStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
}
|
}
|
||||||
err := server.ListenAndServe()
|
err := server.ListenAndServe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error during init SSH Protocol: %s", err.Error())
|
log.Errorf("error during init SSH Protocol: %s", err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"port": beelzebubServiceConfiguration.Address,
|
"port": servConf.Address,
|
||||||
"commands": len(beelzebubServiceConfiguration.Commands),
|
"commands": len(servConf.Commands),
|
||||||
}).Infof("GetInstance service %s", beelzebubServiceConfiguration.Protocol)
|
}).Infof("GetInstance service %s", servConf.Protocol)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,13 @@
|
|||||||
package strategies
|
package TCP
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mariocandela/beelzebub/v3/parser"
|
|
||||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mariocandela/beelzebub/v3/parser"
|
||||||
|
"github.com/mariocandela/beelzebub/v3/tracer"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -14,8 +15,8 @@ import (
|
|||||||
type TCPStrategy struct {
|
type TCPStrategy struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tcpStrategy *TCPStrategy) Init(beelzebubServiceConfiguration parser.BeelzebubServiceConfiguration, tr tracer.Tracer) error {
|
func (tcpStrategy *TCPStrategy) Init(servConf parser.BeelzebubServiceConfiguration, tr tracer.Tracer) error {
|
||||||
listen, err := net.Listen("tcp", beelzebubServiceConfiguration.Address)
|
listen, err := net.Listen("tcp", servConf.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error during init TCP Protocol: %s", err.Error())
|
log.Errorf("Error during init TCP Protocol: %s", err.Error())
|
||||||
return err
|
return err
|
||||||
@ -25,8 +26,8 @@ func (tcpStrategy *TCPStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
for {
|
for {
|
||||||
if conn, err := listen.Accept(); err == nil {
|
if conn, err := listen.Accept(); err == nil {
|
||||||
go func() {
|
go func() {
|
||||||
conn.SetDeadline(time.Now().Add(time.Duration(beelzebubServiceConfiguration.DeadlineTimeoutSeconds) * time.Second))
|
conn.SetDeadline(time.Now().Add(time.Duration(servConf.DeadlineTimeoutSeconds) * time.Second))
|
||||||
conn.Write([]byte(fmt.Sprintf("%s\n", beelzebubServiceConfiguration.Banner)))
|
conn.Write(fmt.Appendf([]byte{}, "%s\n", servConf.Banner))
|
||||||
|
|
||||||
buffer := make([]byte, 1024)
|
buffer := make([]byte, 1024)
|
||||||
command := ""
|
command := ""
|
||||||
@ -46,7 +47,7 @@ func (tcpStrategy *TCPStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
SourceIp: host,
|
SourceIp: host,
|
||||||
SourcePort: port,
|
SourcePort: port,
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
Description: beelzebubServiceConfiguration.Description,
|
Description: servConf.Description,
|
||||||
})
|
})
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}()
|
}()
|
||||||
@ -55,8 +56,8 @@ func (tcpStrategy *TCPStrategy) Init(beelzebubServiceConfiguration parser.Beelze
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"port": beelzebubServiceConfiguration.Address,
|
"port": servConf.Address,
|
||||||
"banner": beelzebubServiceConfiguration.Banner,
|
"banner": servConf.Banner,
|
||||||
}).Infof("Init service %s", beelzebubServiceConfiguration.Protocol)
|
}).Infof("Init service %s", servConf.Protocol)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
@ -1,157 +0,0 @@
|
|||||||
package strategies
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/mariocandela/beelzebub/v3/parser"
|
|
||||||
"github.com/mariocandela/beelzebub/v3/plugins"
|
|
||||||
"github.com/mariocandela/beelzebub/v3/tracer"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HTTPStrategy struct {
|
|
||||||
beelzebubServiceConfiguration parser.BeelzebubServiceConfiguration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (httpStrategy HTTPStrategy) Init(beelzebubServiceConfiguration parser.BeelzebubServiceConfiguration, tr tracer.Tracer) error {
|
|
||||||
httpStrategy.beelzebubServiceConfiguration = beelzebubServiceConfiguration
|
|
||||||
serverMux := http.NewServeMux()
|
|
||||||
|
|
||||||
serverMux.HandleFunc("/", func(responseWriter http.ResponseWriter, request *http.Request) {
|
|
||||||
traceRequest(request, tr, beelzebubServiceConfiguration.Description)
|
|
||||||
for _, command := range httpStrategy.beelzebubServiceConfiguration.Commands {
|
|
||||||
matched, err := regexp.MatchString(command.Regex, request.RequestURI)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error regex: %s, %s", command.Regex, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if matched {
|
|
||||||
responseHTTPBody := command.Handler
|
|
||||||
|
|
||||||
if command.Plugin == plugins.LLMPluginName {
|
|
||||||
|
|
||||||
llmProvider, err := plugins.FromStringToLLMProvider(beelzebubServiceConfiguration.Plugin.LLMProvider)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error: %s", err.Error())
|
|
||||||
responseHTTPBody = "404 Not Found!"
|
|
||||||
}
|
|
||||||
|
|
||||||
llmHoneypot := plugins.LLMHoneypot{
|
|
||||||
Histories: make([]plugins.Message, 0),
|
|
||||||
OpenAIKey: beelzebubServiceConfiguration.Plugin.OpenAISecretKey,
|
|
||||||
Protocol: tracer.HTTP,
|
|
||||||
Host: beelzebubServiceConfiguration.Plugin.Host,
|
|
||||||
Model: beelzebubServiceConfiguration.Plugin.LLMModel,
|
|
||||||
Provider: llmProvider,
|
|
||||||
CustomPrompt: beelzebubServiceConfiguration.Plugin.Prompt,
|
|
||||||
}
|
|
||||||
|
|
||||||
llmHoneypotInstance := plugins.InitLLMHoneypot(llmHoneypot)
|
|
||||||
|
|
||||||
command := fmt.Sprintf("%s %s", request.Method, request.RequestURI)
|
|
||||||
|
|
||||||
if completions, err := llmHoneypotInstance.ExecuteModel(command); err != nil {
|
|
||||||
log.Errorf("Error ExecuteModel: %s, %s", command, err.Error())
|
|
||||||
responseHTTPBody = "404 Not Found!"
|
|
||||||
} else {
|
|
||||||
responseHTTPBody = completions
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
setResponseHeaders(responseWriter, command.Headers, command.StatusCode)
|
|
||||||
fmt.Fprint(responseWriter, responseHTTPBody)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
go func() {
|
|
||||||
var err error
|
|
||||||
// Launch a TLS supporting server if we are supplied a TLS Key and Certificate.
|
|
||||||
// If relative paths are supplied, they are relative to the CWD of the binary.
|
|
||||||
// The can be self-signed, only the client will validate this (or not).
|
|
||||||
if httpStrategy.beelzebubServiceConfiguration.TLSKeyPath != "" && httpStrategy.beelzebubServiceConfiguration.TLSCertPath != "" {
|
|
||||||
err = http.ListenAndServeTLS(
|
|
||||||
httpStrategy.beelzebubServiceConfiguration.Address,
|
|
||||||
httpStrategy.beelzebubServiceConfiguration.TLSCertPath,
|
|
||||||
httpStrategy.beelzebubServiceConfiguration.TLSKeyPath,
|
|
||||||
serverMux)
|
|
||||||
} else {
|
|
||||||
err = http.ListenAndServe(httpStrategy.beelzebubServiceConfiguration.Address, serverMux)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error during init HTTP Protocol: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"port": beelzebubServiceConfiguration.Address,
|
|
||||||
"commands": len(beelzebubServiceConfiguration.Commands),
|
|
||||||
}).Infof("Init service: %s", beelzebubServiceConfiguration.Description)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func traceRequest(request *http.Request, tr tracer.Tracer, HoneypotDescription string) {
|
|
||||||
bodyBytes, err := io.ReadAll(request.Body)
|
|
||||||
body := ""
|
|
||||||
if err == nil {
|
|
||||||
body = string(bodyBytes)
|
|
||||||
}
|
|
||||||
host, port, _ := net.SplitHostPort(request.RemoteAddr)
|
|
||||||
|
|
||||||
event := tracer.Event{
|
|
||||||
Msg: "HTTP New request",
|
|
||||||
RequestURI: request.RequestURI,
|
|
||||||
Protocol: tracer.HTTP.String(),
|
|
||||||
HTTPMethod: request.Method,
|
|
||||||
Body: body,
|
|
||||||
HostHTTPRequest: request.Host,
|
|
||||||
UserAgent: request.UserAgent(),
|
|
||||||
Cookies: mapCookiesToString(request.Cookies()),
|
|
||||||
Headers: request.Header,
|
|
||||||
Status: tracer.Stateless.String(),
|
|
||||||
RemoteAddr: request.RemoteAddr,
|
|
||||||
SourceIp: host,
|
|
||||||
SourcePort: port,
|
|
||||||
ID: uuid.New().String(),
|
|
||||||
Description: HoneypotDescription,
|
|
||||||
}
|
|
||||||
// Capture the TLS details from the request, if provided.
|
|
||||||
if request.TLS != nil {
|
|
||||||
event.Msg = "HTTPS New Request"
|
|
||||||
event.TLSServerName = request.TLS.ServerName
|
|
||||||
}
|
|
||||||
tr.TraceEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapCookiesToString(cookies []*http.Cookie) string {
|
|
||||||
cookiesString := ""
|
|
||||||
|
|
||||||
for _, cookie := range cookies {
|
|
||||||
cookiesString += cookie.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return cookiesString
|
|
||||||
}
|
|
||||||
|
|
||||||
func setResponseHeaders(responseWriter http.ResponseWriter, headers []string, statusCode int) {
|
|
||||||
for _, headerStr := range headers {
|
|
||||||
keyValue := strings.Split(headerStr, ":")
|
|
||||||
if len(keyValue) > 1 {
|
|
||||||
responseWriter.Header().Add(keyValue[0], keyValue[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// http.StatusText(statusCode): empty string if the code is unknown.
|
|
||||||
if len(http.StatusText(statusCode)) > 0 {
|
|
||||||
responseWriter.WriteHeader(statusCode)
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user