From 655eb06b438e4abd4a4b81391443ffa70e0eedc1 Mon Sep 17 00:00:00 2001 From: terrorbyte Date: Fri, 16 May 2025 15:58:56 -0600 Subject: [PATCH 01/11] Add SemVer constraint wrapper and add framework test files --- framework.go | 22 ++++++++++++++++++++++ framework_test.go | 34 ++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ 4 files changed, 59 insertions(+) create mode 100644 framework_test.go diff --git a/framework.go b/framework.go index ea02fc3..51f83f3 100644 --- a/framework.go +++ b/framework.go @@ -71,6 +71,7 @@ import ( "sync/atomic" "time" + "github.com/Masterminds/semver" "github.com/vulncheck-oss/go-exploit/c2" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/cli" @@ -428,6 +429,27 @@ func StoreVersion(conf *config.Config, version string) { db.UpdateVerified(conf.Product, true, version, conf.Rhost, conf.Rport) } +// Compare a version to a semantic version constraint using the [Masterminds semver constraints](https://github.com/Masterminds/semver?tab=readme-ov-file#checking-version-constraints). +// Provide a version string and a constraint and if the semver is within the constraint a boolean +// response of whether the version is constrained or not will occur. Any errors from the constraint +// or version will propagate through the framework errors and the value will be false. +func CheckSemVer(version string, constraint string) bool { + c, err := semver.NewConstraint(constraint) + if err != nil { + output.PrintfFrameworkError("Invalid constraint: %s", err.Error()) + + return false + } + v, err := semver.NewVersion(version) + if err != nil { + output.PrintfFrameworkError("Invalid version: %s", err.Error()) + + return false + } + + return c.Check(v) +} + // modify godebug to re-enable old cipher suites that were removed in 1.22. This does have implications for our // client fingerprint, and we should consider how to improve/fix that in the future. We also should be respectful // of other disabling this feature, so we will check for it before re-enabling it. diff --git a/framework_test.go b/framework_test.go new file mode 100644 index 0000000..0c79c48 --- /dev/null +++ b/framework_test.go @@ -0,0 +1,34 @@ +package exploit_test + +import ( + "testing" + + "github.com/vulncheck-oss/go-exploit" +) + +func TestCheckSemVer_Full(t *testing.T) { + if !exploit.CheckSemVer("1.0.0", "<= 1.0.0") { + t.Error("Constraint should have passed") + } + if exploit.CheckSemVer("1.0.0", "> 1.0.0") { + t.Error("Constraint should not have passed") + } +} + +func TestCheckSemVer_BadVersion(t *testing.T) { + if exploit.CheckSemVer("uwu", "<= 1.0.0") { + t.Error("Version was invalid, should not have passed") + } + if exploit.CheckSemVer("1.0.0 ", "<= 1.0.0") { + t.Error("Version was invalid, should not have passed") + } +} + +func TestCheckSemVer_BadConstraint(t *testing.T) { + if exploit.CheckSemVer("1.0.0", "<== 1.0.0") { + t.Error("Constraint was invalid, should not have passed") + } + if exploit.CheckSemVer("1.0.0", "xp") { + t.Error("Constraint was invalid, should not have passed") + } +} diff --git a/go.mod b/go.mod index 023522b..16b33de 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( ) require ( + github.com/Masterminds/semver v1.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index 28329c7..c3320b9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= From 71fc158dbb772aa0bc1fd2742560be79f94e5dd0 Mon Sep 17 00:00:00 2001 From: lobsterjerusalem Date: Mon, 19 May 2025 15:13:20 -0600 Subject: [PATCH 02/11] Added wrappers with file serve --- wrappers/wrappers.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 wrappers/wrappers.go diff --git a/wrappers/wrappers.go b/wrappers/wrappers.go new file mode 100644 index 0000000..be0bcaa --- /dev/null +++ b/wrappers/wrappers.go @@ -0,0 +1,32 @@ +package wrappers + +import( + "github.com/vulncheck-oss/go-exploit/config" + "github.com/vulncheck-oss/go-exploit/output" + "github.com/vulncheck-oss/go-exploit/c2/channel" + "github.com/vulncheck-oss/go-exploit/c2/httpservefile" +) + +// A collection of wrappers/helper functions that have otherwise no place to go and may need to be in their own packages +// to avoid certain import cycle errors. + +// Helper function for use within exploits to reduce the overall amount of boilerplate when setting up a file server to host a dynamically generated file. +func HTTPServeFileInitAndRunWithFile(conf *config.Config, fileName string, routeName string, data[]byte) bool { + httpServer := httpservefile.GetInstance() + + if httpServer.HTTPAddr == "" { + httpServer.HTTPAddr = conf.Lhost + } + + if !httpServer.Init(&channel.Channel{HTTPAddr: httpServer.HTTPAddr, HTTPPort: httpServer.HTTPPort}) { + output.PrintFrameworkError("Could not start http server") + + return false + } + + httpServer.AddFile(fileName, routeName, data) + + go httpServer.Run(conf.C2Timeout) + + return true +} From eeb8cafd89f81a15c4c7e2d12a993385b41e1ca6 Mon Sep 17 00:00:00 2001 From: lobsterjerusalem Date: Mon, 19 May 2025 15:23:52 -0600 Subject: [PATCH 03/11] Added expanded version checking wrapper --- wrappers/wrappers.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/wrappers/wrappers.go b/wrappers/wrappers.go index be0bcaa..9e685b1 100644 --- a/wrappers/wrappers.go +++ b/wrappers/wrappers.go @@ -1,8 +1,13 @@ package wrappers import( + "regexp" + "net/http" + + "github.com/vulncheck-oss/go-exploit" "github.com/vulncheck-oss/go-exploit/config" "github.com/vulncheck-oss/go-exploit/output" + "github.com/vulncheck-oss/go-exploit/protocol" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/c2/httpservefile" ) @@ -30,3 +35,36 @@ func HTTPServeFileInitAndRunWithFile(conf *config.Config, fileName string, route return true } + +// This removes generic version checking boiler plate that works in a great deal of cases. +// Of course it will not work in every case but that is not the point. +func SimpleVersionCheck(conf *config.Config, rePattern string, versionConstraint string) exploit.VersionCheckType { + url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/") + resp, body, ok := protocol.HTTPGetCache(url) + if !ok || resp == nil { + return exploit.Unknown + } + + if resp.StatusCode != http.StatusOK { + output.PrintfFrameworkError("Version check failed: Unexpected response code during initial GET request: %d", resp.StatusCode) + + return exploit.Unknown + } + + matches := regexp.MustCompile(rePattern).FindStringSubmatch(body) + if len(matches) < 2 { + output.PrintFrameworkError("Version check failed: No matches found for the provided pattern") + + return exploit.Unknown + } + + exploit.StoreVersion(conf, matches[1]) + + if exploit.CheckSemVer(matches[1], versionConstraint) { + return exploit.Vulnerable + } + + output.PrintFrameworkError("Version did not match the constraint") + + return exploit.NotVulnerable +} From 76a7b3088a0c123fce3752503e5c972e910f8e2c Mon Sep 17 00:00:00 2001 From: lobsterjerusalem Date: Mon, 19 May 2025 15:43:59 -0600 Subject: [PATCH 04/11] removed output line --- wrappers/wrappers.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/wrappers/wrappers.go b/wrappers/wrappers.go index 9e685b1..671c096 100644 --- a/wrappers/wrappers.go +++ b/wrappers/wrappers.go @@ -64,7 +64,5 @@ func SimpleVersionCheck(conf *config.Config, rePattern string, versionConstraint return exploit.Vulnerable } - output.PrintFrameworkError("Version did not match the constraint") - return exploit.NotVulnerable } From 3de3440a6c312e7c36a1b3a70fc2b9e4c1ab9b4a Mon Sep 17 00:00:00 2001 From: lobsterjerusalem Date: Wed, 21 May 2025 10:52:04 -0600 Subject: [PATCH 05/11] init first draft of start/get listeners --- api/http/handlers.go | 144 +++++++++++++++++++++++++ api/http/http.go | 141 ++++++++++++++++++++++++ api/listenermanager/listenermanager.go | 85 +++++++++++++++ api/testapi/test.go | 10 ++ api/types/types.go | 16 +++ 5 files changed, 396 insertions(+) create mode 100644 api/http/handlers.go create mode 100644 api/http/http.go create mode 100644 api/listenermanager/listenermanager.go create mode 100644 api/testapi/test.go create mode 100644 api/types/types.go diff --git a/api/http/handlers.go b/api/http/handlers.go new file mode 100644 index 0000000..5dc7401 --- /dev/null +++ b/api/http/handlers.go @@ -0,0 +1,144 @@ +package http + +import ( + "strconv" + "net/http" + "encoding/json" + + "github.com/vulncheck-oss/go-exploit/output" + "github.com/vulncheck-oss/go-exploit/c2/channel" + "github.com/vulncheck-oss/go-exploit/api/listenermanager" + "github.com/vulncheck-oss/go-exploit/api/types" +) + +type APIResponse struct { + Status string `json:"status"` + Data string `json:"data"` +} + +type StartListenersAPIResponse struct { + Status string `json:"status"` + Data map[string]string `json:"data"` +} + +type GetListenersAPIResponse struct { + Status string `json:"status"` + Data []listenermanager.GetListenersResponse `json:"data"` +} + + +func sendAPIResponse(w http.ResponseWriter, status string, errMessage string){ + output.PrintFrameworkError(errMessage) + if err := json.NewEncoder(w).Encode(&APIResponse{Status: status, Data: errMessage}); err != nil { + http.Error(w, "Failed to encode JSON reseponse", http.StatusInternalServerError) + + return + } +} + + +func startListener(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + sendAPIResponse(w, "failure", "Invalid request method") + + return + } + + var data map[string]string + + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + sendAPIResponse(w, "failure", "Invalid JSON") + + return + } + + // Decision point: instead of decoding into a struct, doing it this way so it's easier to debug + serverType, ok := data[types.ServerType] + if !ok { + sendAPIResponse(w, "failure", "Missing serverType") + + return + } + + serverAddr, ok := data[types.ServerAddr] + if !ok { + sendAPIResponse(w, "failure", "Missing serverAddr") + + return + } + + serverPort, ok := data[types.ServerPort] + if !ok { + sendAPIResponse(w, "failure", "Missing serverPort") + + return + } + serverPortInt, err := strconv.Atoi(serverPort) + if err != nil { + sendAPIResponse(w, "failure", "Invalid server port, could not parse as int") + + return + } + + serverTimeout, ok := data[types.ServerTimeout] + if !ok { + output.PrintFrameworkWarn("No serverTimeout provided, using 30 instead") + serverTimeout = "30" + } + serverTimeoutInt, err := strconv.Atoi(serverTimeout) + if err != nil { + sendAPIResponse(w, "failure", "Invalid server timeout, could not parse as int") + + return + } + + serverChannel := channel.Channel{Port: serverPortInt, Timeout: serverTimeoutInt, HTTPAddr: serverAddr, HTTPPort: serverPortInt, IPAddr: serverAddr} + uuid, ok := listenermanager.GetInstance().CreateListener(serverType, serverChannel) + if !ok { + sendAPIResponse(w, "failure", "Failed to start listener") + + return + } + + uuidData := map[string]string { "uuid": uuid } + if err := json.NewEncoder(w).Encode(&StartListenersAPIResponse{Status: "success", Data: uuidData}); err != nil { + http.Error(w, "Failed to encode JSON reseponse", http.StatusInternalServerError) + + return + } +} + +func getListeners(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + sendAPIResponse(w, "failure", "Invalid request method") + + return + } + + responseArray, ok := listenermanager.GetInstance().GetListeners() + if !ok { + sendAPIResponse(w, "failure", "Failed to retrieve listeners") + + return + } + + w.Header().Set("Content-Type", "application/json") + + if len(responseArray) == 0 { + sendAPIResponse(w, "success", "[]") + + return + } + + if err := json.NewEncoder(w).Encode(&GetListenersAPIResponse{Status: "success", Data: responseArray}); err != nil { + http.Error(w, "Failed to encode JSON reseponse", http.StatusInternalServerError) + + return + } +} + +//func StopListener(w http.ResponseWriter, r *http.Request) { +//} +//func StartListener(w http.ResponseWriter, r *http.Request) { +//} diff --git a/api/http/http.go b/api/http/http.go new file mode 100644 index 0000000..f50cb0e --- /dev/null +++ b/api/http/http.go @@ -0,0 +1,141 @@ +package http + +import ( + "fmt" + "sync" + "time" + "net/http" + "crypto/tls" + "sync/atomic" + + "github.com/vulncheck-oss/go-exploit/output" + "github.com/vulncheck-oss/go-exploit/encryption" +) + +type Server struct { + // signals the shutdown + Shutdown *atomic.Bool + // specifies if https should be used + HTTPS bool + // port that the api listens on + Port int + // addr that api listens on + Addr string + // handle to use for shutting down the server + ServerHandle http.Server + // ssl cert + PrivateKeyFile string + // ssl private key + CertificateFile string + // loaded certificate + Certificate tls.Certificate +} + +var wg sync.WaitGroup +var serverSingleton *Server + +// The singleton interface for the Socks over HTTPS server. +func GetInstance() *Server { + if serverSingleton == nil { + serverSingleton = new(Server) + } + + return serverSingleton +} + +// setup certs and vars +func (server *Server) Init(Addr string, Port int) bool { + server.Addr = Addr + server.Port = Port + + if server.Shutdown == nil { + // Initialize the shutdown atomic. This lets us not have to define it if the C2 is manually + // configured. + var shutdown atomic.Bool + shutdown.Store(false) + server.Shutdown = &shutdown + } + + if server.HTTPS { + var ok bool + var err error + if len(server.CertificateFile) != 0 && len(server.PrivateKeyFile) != 0 { + server.Certificate, err = tls.LoadX509KeyPair(server.CertificateFile, server.PrivateKeyFile) + if err != nil { + output.PrintfFrameworkError("Error loading certificate: %s", err.Error()) + + return false + } + } else { + output.PrintFrameworkStatus("Certificate not provided. Generating a TLS Certificate") + server.Certificate, ok = encryption.GenerateCertificate() + if !ok { + return false + } + } + } + + return true +} + +func (server *Server) Run() { + defer server.Shutdown.Store(true) + sockAddr:= fmt.Sprintf("%s:%d", server.Addr, server.Port) + APIMux := http.NewServeMux() + // assign handlers + APIMux.HandleFunc("/startListener", startListener) + APIMux.HandleFunc("/getListeners", getListeners) + + // start the server in a go routine, can kill it with the serverHandle + wg.Add(1) + go func() { + defer wg.Done() + if server.HTTPS { + output.PrintfFrameworkStatus("Starting HTTPS API Server on: %s", sockAddr) + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{server.Certificate}, + MinVersion: tls.VersionSSL30, // TODO remove this probably + } + server.ServerHandle = http.Server{ + Addr: sockAddr, + TLSConfig: tlsConfig, + Handler: APIMux, + } + _ = server.ServerHandle.ListenAndServeTLS("", "") + } else { + output.PrintfFrameworkStatus("Starting HTTP API Server on: %s", sockAddr) + server.ServerHandle = http.Server{ + Addr: sockAddr, + Handler: APIMux, + } + _ = server.ServerHandle.ListenAndServe() + } + }() + + output.PrintFrameworkStatus("API server started successfully") + defer server.ServerHandle.Close() // TODO revist whether or not this needs to exist + + wg.Add(1) + go func() { + defer wg.Done() + for { + if server.Shutdown.Load() { + server.ServerHandle.Close() + wg.Done() + + break + } + time.Sleep(10 * time.Millisecond) + } + }() + + wg.Wait() +} + +//func (server *Server) CreateFlags() { + //if flag.Lookup("sslShellServer.PrivateKeyFile") == nil { + //flag.StringVar(&shellServer.PrivateKeyFile, "sslShellServer.PrivateKeyFile", "", "A private key to use with the SSL server") + //flag.StringVar(&shellServer.CertificateFile, "sslShellServer.CertificateFile", "", "The certificate to use with the SSL server") + //} +//} + diff --git a/api/listenermanager/listenermanager.go b/api/listenermanager/listenermanager.go new file mode 100644 index 0000000..6803e57 --- /dev/null +++ b/api/listenermanager/listenermanager.go @@ -0,0 +1,85 @@ +package listenermanager + +import ( + _ "encoding/json" + "github.com/google/uuid" + "github.com/vulncheck-oss/go-exploit/c2/channel" + + "github.com/vulncheck-oss/go-exploit/c2/simpleshell" + "github.com/vulncheck-oss/go-exploit/c2/sslshell" + "github.com/vulncheck-oss/go-exploit/output" + "github.com/vulncheck-oss/go-exploit/api/types" +) + +var singleton *ListenerManager + +func GetInstance() *ListenerManager { + if singleton == nil { + singleton = new(ListenerManager) + singleton.SimpleShellServers = make(map[string]*simpleshell.Server) + singleton.SSLShellServers = make(map[string]*sslshell.Server) + } + + return singleton +} + +type ListenerManager struct { + // holds handles to simpleshell servers + SimpleShellServers map[string]*simpleshell.Server + + // holds handles to sslshell servers + SSLShellServers map[string]*sslshell.Server + +} + +type GetListenersResponse struct { + Uuid string `json:"uuid"` + Ipaddr string `json:"ipaddr"` + Port int `json:"port"` + Httpaddr string `json:"httpaddr"` + Httpport int `json:"httpport"` + Active bool `json:"active"` + Hassessions bool `json:"hassessions"` +} + +func (manager *ListenerManager) GetListeners() ([]GetListenersResponse, bool) { + var retArray []GetListenersResponse + for key, item := range manager.SimpleShellServers { + retArray = append(retArray, GetListenersResponse { + Uuid: key, + Ipaddr: item.Channel().IPAddr, + Port: item.Channel().Port, + Httpaddr: item.Channel().HTTPAddr, + Httpport: item.Channel().HTTPPort, + Hassessions: item.Channel().HasSessions(), + Active: !item.Channel().Shutdown.Load(), + }) + } + + return retArray, true +} + +func (manager *ListenerManager) CreateListener(serverType string, serverChan channel.Channel) (string, bool) { + var ok bool + switch serverType { + case types.SimpleShellServer: + serverStruct := new(simpleshell.Server) + ok = serverStruct.Init(&serverChan) + if !ok { + output.PrintFrameworkError("Failed to init simple shell server") + + return "", false + } + go serverStruct.Run(serverChan.Timeout) + // TODO put additional metadata in the map like cve that called it + serverUUID := uuid.New().String() + manager.SimpleShellServers[serverUUID] = serverStruct + return serverUUID, true + case types.SSLShellServer: + output.PrintFrameworkError("Not implemented") // TODO make tis one + return "", false + default: + output.PrintFrameworkError("CreateListener called with invalid listenertype") + return "", false + } +} diff --git a/api/testapi/test.go b/api/testapi/test.go new file mode 100644 index 0000000..f46b5ec --- /dev/null +++ b/api/testapi/test.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/vulncheck-oss/go-exploit/api/http" +) +func main() { + http.GetInstance().Init("127.0.0.1", 2828) + http.GetInstance().Run() +} + diff --git a/api/types/types.go b/api/types/types.go new file mode 100644 index 0000000..a93dd73 --- /dev/null +++ b/api/types/types.go @@ -0,0 +1,16 @@ +package types +// this package exists to hold constants for expected api key/value + +// keys for api requests +const ( + ServerType = "serverType" + ServerAddr = "serverAddr" + ServerPort = "serverPort" + ServerTimeout = "serverTimeout" +) + +// expected values for api requests +const ( + SimpleShellServer = "simpleshellserver" + SSLShellServer = "sslshellserver" +) From 453051a90b061cabbcd2aa991cf8a54b3bff8d5a Mon Sep 17 00:00:00 2001 From: lobsterjerusalem Date: Wed, 21 May 2025 11:14:01 -0600 Subject: [PATCH 06/11] Added stoplistener --- api/http/handlers.go | 48 ++++++++++++++++++++------ api/http/http.go | 1 + api/listenermanager/listenermanager.go | 20 +++++++++++ go.mod | 4 +-- 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/api/http/handlers.go b/api/http/handlers.go index 5dc7401..d1f2284 100644 --- a/api/http/handlers.go +++ b/api/http/handlers.go @@ -26,14 +26,21 @@ type GetListenersAPIResponse struct { Data []listenermanager.GetListenersResponse `json:"data"` } +type StopListenerRequest struct { + UUID string `json:"uuid"` +} -func sendAPIResponse(w http.ResponseWriter, status string, errMessage string){ - output.PrintFrameworkError(errMessage) - if err := json.NewEncoder(w).Encode(&APIResponse{Status: status, Data: errMessage}); err != nil { - http.Error(w, "Failed to encode JSON reseponse", http.StatusInternalServerError) - return - } +func sendAPIResponse(w http.ResponseWriter, status string, errMessage string) { + if status == "failure" { + w.WriteHeader(http.StatusBadRequest) + } + response := APIResponse{Status: status, Data: errMessage} + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, "Failed to encode JSON response", http.StatusInternalServerError) + + return + } } @@ -138,7 +145,28 @@ func getListeners(w http.ResponseWriter, r *http.Request) { } } -//func StopListener(w http.ResponseWriter, r *http.Request) { -//} -//func StartListener(w http.ResponseWriter, r *http.Request) { -//} +func stopListener(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + sendAPIResponse(w, "failure", "Invalid request method") + + return + } + + var request StopListenerRequest + + err := json.NewDecoder(r.Body).Decode(&request) + if err != nil { + sendAPIResponse(w, "failure", "Invalid request provided") + + return + } + + ok := listenermanager.GetInstance().StopListener(request.UUID) + if !ok { + sendAPIResponse(w, "failure", "Failed to shutdown server") + + return + } + + sendAPIResponse(w, "success", "Successfully shutdown server") +} diff --git a/api/http/http.go b/api/http/http.go index f50cb0e..39cce60 100644 --- a/api/http/http.go +++ b/api/http/http.go @@ -84,6 +84,7 @@ func (server *Server) Run() { APIMux := http.NewServeMux() // assign handlers APIMux.HandleFunc("/startListener", startListener) + APIMux.HandleFunc("/stopListener", stopListener) APIMux.HandleFunc("/getListeners", getListeners) // start the server in a go routine, can kill it with the serverHandle diff --git a/api/listenermanager/listenermanager.go b/api/listenermanager/listenermanager.go index 6803e57..8bcc8c1 100644 --- a/api/listenermanager/listenermanager.go +++ b/api/listenermanager/listenermanager.go @@ -42,6 +42,26 @@ type GetListenersResponse struct { Hassessions bool `json:"hassessions"` } +func (manager *ListenerManager) StopListener(uuid string ) bool { + var ok bool + for key, item := range manager.SimpleShellServers { + if key == uuid { + output.PrintFrameworkDebug("Found UUID requested for shutdown") + ok = item.Shutdown() + if ok { + break + } + } + } + if ok { + delete(manager.SimpleShellServers, uuid) + return true + } + + output.PrintFrameworkError("Could not shutdown server, failed to find UUID") + return false +} + func (manager *ListenerManager) GetListeners() ([]GetListenersResponse, bool) { var retArray []GetListenersResponse for key, item := range manager.SimpleShellServers { diff --git a/go.mod b/go.mod index 16b33de..7b3e23d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/vulncheck-oss/go-exploit go 1.24.1 require ( + github.com/Masterminds/semver v1.5.0 + github.com/google/uuid v1.6.0 github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 github.com/vjeantet/ldapserver v1.0.2-0.20240305064909-a417792e2906 golang.org/x/crypto v0.38.0 @@ -12,9 +14,7 @@ require ( ) require ( - github.com/Masterminds/semver v1.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect From 6ecb77f410b5f9948d89e51c606cd26cec846281 Mon Sep 17 00:00:00 2001 From: lobsterjerusalem Date: Wed, 21 May 2025 12:15:41 -0600 Subject: [PATCH 07/11] lint --- api/http/handlers.go | 52 +++++++++++++------------- api/http/http.go | 37 +++++++++--------- api/listenermanager/listenermanager.go | 48 +++++++++++++----------- api/testapi/test.go | 2 +- api/types/types.go | 13 ++++--- 5 files changed, 78 insertions(+), 74 deletions(-) diff --git a/api/http/handlers.go b/api/http/handlers.go index d1f2284..40d22a2 100644 --- a/api/http/handlers.go +++ b/api/http/handlers.go @@ -1,53 +1,51 @@ package http import ( - "strconv" - "net/http" "encoding/json" + "net/http" + "strconv" - "github.com/vulncheck-oss/go-exploit/output" - "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/api/listenermanager" "github.com/vulncheck-oss/go-exploit/api/types" + "github.com/vulncheck-oss/go-exploit/c2/channel" + "github.com/vulncheck-oss/go-exploit/output" ) type APIResponse struct { Status string `json:"status"` - Data string `json:"data"` + Data string `json:"data"` } type StartListenersAPIResponse struct { - Status string `json:"status"` - Data map[string]string `json:"data"` + Status string `json:"status"` + Data map[string]string `json:"data"` } type GetListenersAPIResponse struct { - Status string `json:"status"` - Data []listenermanager.GetListenersResponse `json:"data"` + Status string `json:"status"` + Data []listenermanager.GetListenersResponse `json:"data"` } type StopListenerRequest struct { UUID string `json:"uuid"` } - func sendAPIResponse(w http.ResponseWriter, status string, errMessage string) { - if status == "failure" { - w.WriteHeader(http.StatusBadRequest) - } - response := APIResponse{Status: status, Data: errMessage} - if err := json.NewEncoder(w).Encode(response); err != nil { - http.Error(w, "Failed to encode JSON response", http.StatusInternalServerError) - - return - } -} + if status == "failure" { + w.WriteHeader(http.StatusBadRequest) + } + response := APIResponse{Status: status, Data: errMessage} + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, "Failed to encode JSON response", http.StatusInternalServerError) + return + } +} func startListener(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { sendAPIResponse(w, "failure", "Invalid request method") - + return } @@ -84,8 +82,8 @@ func startListener(w http.ResponseWriter, r *http.Request) { serverPortInt, err := strconv.Atoi(serverPort) if err != nil { sendAPIResponse(w, "failure", "Invalid server port, could not parse as int") - - return + + return } serverTimeout, ok := data[types.ServerTimeout] @@ -96,8 +94,8 @@ func startListener(w http.ResponseWriter, r *http.Request) { serverTimeoutInt, err := strconv.Atoi(serverTimeout) if err != nil { sendAPIResponse(w, "failure", "Invalid server timeout, could not parse as int") - - return + + return } serverChannel := channel.Channel{Port: serverPortInt, Timeout: serverTimeoutInt, HTTPAddr: serverAddr, HTTPPort: serverPortInt, IPAddr: serverAddr} @@ -108,7 +106,7 @@ func startListener(w http.ResponseWriter, r *http.Request) { return } - uuidData := map[string]string { "uuid": uuid } + uuidData := map[string]string{"uuid": uuid} if err := json.NewEncoder(w).Encode(&StartListenersAPIResponse{Status: "success", Data: uuidData}); err != nil { http.Error(w, "Failed to encode JSON reseponse", http.StatusInternalServerError) @@ -135,7 +133,7 @@ func getListeners(w http.ResponseWriter, r *http.Request) { if len(responseArray) == 0 { sendAPIResponse(w, "success", "[]") - return + return } if err := json.NewEncoder(w).Encode(&GetListenersAPIResponse{Status: "success", Data: responseArray}); err != nil { diff --git a/api/http/http.go b/api/http/http.go index 39cce60..510b543 100644 --- a/api/http/http.go +++ b/api/http/http.go @@ -1,15 +1,15 @@ package http import ( + "crypto/tls" "fmt" - "sync" - "time" "net/http" - "crypto/tls" + "sync" "sync/atomic" - - "github.com/vulncheck-oss/go-exploit/output" + "time" + "github.com/vulncheck-oss/go-exploit/encryption" + "github.com/vulncheck-oss/go-exploit/output" ) type Server struct { @@ -31,8 +31,10 @@ type Server struct { Certificate tls.Certificate } -var wg sync.WaitGroup -var serverSingleton *Server +var ( + wg sync.WaitGroup + serverSingleton *Server +) // The singleton interface for the Socks over HTTPS server. func GetInstance() *Server { @@ -43,7 +45,7 @@ func GetInstance() *Server { return serverSingleton } -// setup certs and vars +// setup certs and vars. func (server *Server) Init(Addr string, Port int) bool { server.Addr = Addr server.Port = Port @@ -80,7 +82,7 @@ func (server *Server) Init(Addr string, Port int) bool { func (server *Server) Run() { defer server.Shutdown.Store(true) - sockAddr:= fmt.Sprintf("%s:%d", server.Addr, server.Port) + sockAddr := fmt.Sprintf("%s:%d", server.Addr, server.Port) APIMux := http.NewServeMux() // assign handlers APIMux.HandleFunc("/startListener", startListener) @@ -95,18 +97,18 @@ func (server *Server) Run() { output.PrintfFrameworkStatus("Starting HTTPS API Server on: %s", sockAddr) tlsConfig := &tls.Config{ Certificates: []tls.Certificate{server.Certificate}, - MinVersion: tls.VersionSSL30, // TODO remove this probably + MinVersion: tls.VersionSSL30, // TODO remove this probably } server.ServerHandle = http.Server{ Addr: sockAddr, TLSConfig: tlsConfig, - Handler: APIMux, + Handler: APIMux, } _ = server.ServerHandle.ListenAndServeTLS("", "") } else { output.PrintfFrameworkStatus("Starting HTTP API Server on: %s", sockAddr) server.ServerHandle = http.Server{ - Addr: sockAddr, + Addr: sockAddr, Handler: APIMux, } _ = server.ServerHandle.ListenAndServe() @@ -133,10 +135,9 @@ func (server *Server) Run() { wg.Wait() } -//func (server *Server) CreateFlags() { - //if flag.Lookup("sslShellServer.PrivateKeyFile") == nil { - //flag.StringVar(&shellServer.PrivateKeyFile, "sslShellServer.PrivateKeyFile", "", "A private key to use with the SSL server") - //flag.StringVar(&shellServer.CertificateFile, "sslShellServer.CertificateFile", "", "The certificate to use with the SSL server") - //} +// func (server *Server) CreateFlags() { +// if flag.Lookup("sslShellServer.PrivateKeyFile") == nil { +// flag.StringVar(&shellServer.PrivateKeyFile, "sslShellServer.PrivateKeyFile", "", "A private key to use with the SSL server") +// flag.StringVar(&shellServer.CertificateFile, "sslShellServer.CertificateFile", "", "The certificate to use with the SSL server") +//} //} - diff --git a/api/listenermanager/listenermanager.go b/api/listenermanager/listenermanager.go index 8bcc8c1..90dde38 100644 --- a/api/listenermanager/listenermanager.go +++ b/api/listenermanager/listenermanager.go @@ -5,19 +5,19 @@ import ( "github.com/google/uuid" "github.com/vulncheck-oss/go-exploit/c2/channel" + "github.com/vulncheck-oss/go-exploit/api/types" "github.com/vulncheck-oss/go-exploit/c2/simpleshell" "github.com/vulncheck-oss/go-exploit/c2/sslshell" "github.com/vulncheck-oss/go-exploit/output" - "github.com/vulncheck-oss/go-exploit/api/types" ) var singleton *ListenerManager func GetInstance() *ListenerManager { if singleton == nil { - singleton = new(ListenerManager) + singleton = new(ListenerManager) singleton.SimpleShellServers = make(map[string]*simpleshell.Server) - singleton.SSLShellServers = make(map[string]*sslshell.Server) + singleton.SSLShellServers = make(map[string]*sslshell.Server) } return singleton @@ -29,20 +29,19 @@ type ListenerManager struct { // holds handles to sslshell servers SSLShellServers map[string]*sslshell.Server - } type GetListenersResponse struct { - Uuid string `json:"uuid"` - Ipaddr string `json:"ipaddr"` - Port int `json:"port"` - Httpaddr string `json:"httpaddr"` - Httpport int `json:"httpport"` - Active bool `json:"active"` - Hassessions bool `json:"hassessions"` + Uuid string `json:"uuid"` + Ipaddr string `json:"ipaddr"` + Port int `json:"port"` + Httpaddr string `json:"httpaddr"` + Httpport int `json:"httpport"` + Active bool `json:"active"` + Hassessions bool `json:"hassessions"` } -func (manager *ListenerManager) StopListener(uuid string ) bool { +func (manager *ListenerManager) StopListener(uuid string) bool { var ok bool for key, item := range manager.SimpleShellServers { if key == uuid { @@ -53,26 +52,31 @@ func (manager *ListenerManager) StopListener(uuid string ) bool { } } } - if ok { + + if ok { delete(manager.SimpleShellServers, uuid) + return true } output.PrintFrameworkError("Could not shutdown server, failed to find UUID") + return false } func (manager *ListenerManager) GetListeners() ([]GetListenersResponse, bool) { + //nolint:prealloc var retArray []GetListenersResponse + for key, item := range manager.SimpleShellServers { - retArray = append(retArray, GetListenersResponse { - Uuid: key, - Ipaddr: item.Channel().IPAddr, - Port: item.Channel().Port, - Httpaddr: item.Channel().HTTPAddr, - Httpport: item.Channel().HTTPPort, + retArray = append(retArray, GetListenersResponse{ + Uuid: key, + Ipaddr: item.Channel().IPAddr, + Port: item.Channel().Port, + Httpaddr: item.Channel().HTTPAddr, + Httpport: item.Channel().HTTPPort, Hassessions: item.Channel().HasSessions(), - Active: !item.Channel().Shutdown.Load(), + Active: !item.Channel().Shutdown.Load(), }) } @@ -94,10 +98,10 @@ func (manager *ListenerManager) CreateListener(serverType string, serverChan cha // TODO put additional metadata in the map like cve that called it serverUUID := uuid.New().String() manager.SimpleShellServers[serverUUID] = serverStruct - return serverUUID, true + return serverUUID, true case types.SSLShellServer: output.PrintFrameworkError("Not implemented") // TODO make tis one - return "", false + return "", false default: output.PrintFrameworkError("CreateListener called with invalid listenertype") return "", false diff --git a/api/testapi/test.go b/api/testapi/test.go index f46b5ec..55df7c4 100644 --- a/api/testapi/test.go +++ b/api/testapi/test.go @@ -3,8 +3,8 @@ package main import ( "github.com/vulncheck-oss/go-exploit/api/http" ) + func main() { http.GetInstance().Init("127.0.0.1", 2828) http.GetInstance().Run() } - diff --git a/api/types/types.go b/api/types/types.go index a93dd73..fd7a016 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -1,16 +1,17 @@ package types + // this package exists to hold constants for expected api key/value // keys for api requests const ( - ServerType = "serverType" - ServerAddr = "serverAddr" - ServerPort = "serverPort" - ServerTimeout = "serverTimeout" + ServerType = "serverType" + ServerAddr = "serverAddr" + ServerPort = "serverPort" + ServerTimeout = "serverTimeout" ) // expected values for api requests const ( - SimpleShellServer = "simpleshellserver" - SSLShellServer = "sslshellserver" + SimpleShellServer = "simpleshellserver" + SSLShellServer = "sslshellserver" ) From dca32070cbe99e0375d37bc9a208e9a8a81cf1cf Mon Sep 17 00:00:00 2001 From: lobsterjerusalem Date: Wed, 21 May 2025 12:28:18 -0600 Subject: [PATCH 08/11] Changed impl to use interface --- api/listenermanager/listenermanager.go | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/api/listenermanager/listenermanager.go b/api/listenermanager/listenermanager.go index 90dde38..836991f 100644 --- a/api/listenermanager/listenermanager.go +++ b/api/listenermanager/listenermanager.go @@ -1,13 +1,14 @@ package listenermanager import ( - _ "encoding/json" + _ "encoding/json" // for json var-naming + "github.com/google/uuid" + "github.com/vulncheck-oss/go-exploit/c2" "github.com/vulncheck-oss/go-exploit/c2/channel" - "github.com/vulncheck-oss/go-exploit/api/types" "github.com/vulncheck-oss/go-exploit/c2/simpleshell" - "github.com/vulncheck-oss/go-exploit/c2/sslshell" + // "github.com/vulncheck-oss/go-exploit/c2/sslshell" "github.com/vulncheck-oss/go-exploit/output" ) @@ -16,19 +17,15 @@ var singleton *ListenerManager func GetInstance() *ListenerManager { if singleton == nil { singleton = new(ListenerManager) - singleton.SimpleShellServers = make(map[string]*simpleshell.Server) - singleton.SSLShellServers = make(map[string]*sslshell.Server) + singleton.ServersMap= make(map[string]c2.Interface) } return singleton } type ListenerManager struct { - // holds handles to simpleshell servers - SimpleShellServers map[string]*simpleshell.Server - - // holds handles to sslshell servers - SSLShellServers map[string]*sslshell.Server + // holds handles to servers + ServersMap map[string]c2.Interface } type GetListenersResponse struct { @@ -43,7 +40,7 @@ type GetListenersResponse struct { func (manager *ListenerManager) StopListener(uuid string) bool { var ok bool - for key, item := range manager.SimpleShellServers { + for key, item := range manager.ServersMap { if key == uuid { output.PrintFrameworkDebug("Found UUID requested for shutdown") ok = item.Shutdown() @@ -54,7 +51,7 @@ func (manager *ListenerManager) StopListener(uuid string) bool { } if ok { - delete(manager.SimpleShellServers, uuid) + delete(manager.ServersMap, uuid) return true } @@ -68,7 +65,7 @@ func (manager *ListenerManager) GetListeners() ([]GetListenersResponse, bool) { //nolint:prealloc var retArray []GetListenersResponse - for key, item := range manager.SimpleShellServers { + for key, item := range manager.ServersMap { retArray = append(retArray, GetListenersResponse{ Uuid: key, Ipaddr: item.Channel().IPAddr, @@ -97,13 +94,15 @@ func (manager *ListenerManager) CreateListener(serverType string, serverChan cha go serverStruct.Run(serverChan.Timeout) // TODO put additional metadata in the map like cve that called it serverUUID := uuid.New().String() - manager.SimpleShellServers[serverUUID] = serverStruct + manager.ServersMap[serverUUID] = serverStruct return serverUUID, true case types.SSLShellServer: - output.PrintFrameworkError("Not implemented") // TODO make tis one + output.PrintFrameworkError("Not implemented") // TODO make this one + return "", false default: output.PrintFrameworkError("CreateListener called with invalid listenertype") + return "", false } } From 870a5504b2f480b5cb641691a4123df2a29705df Mon Sep 17 00:00:00 2001 From: lobsterjerusalem Date: Wed, 21 May 2025 16:45:40 -0600 Subject: [PATCH 09/11] added client go-xploit code for startlistener, added api server resolution --- api/client/client.go | 60 +++++++++++++ api/http/handlers.go | 75 +++++++--------- api/http/http.go | 1 + api/listenermanager/listenermanager.go | 6 +- c2/channel/channel.go | 21 ++--- c2/cli/managed.go | 119 +++++++++++++++++++++++++ c2/simpleshell/simpleshellserver.go | 24 +++-- cli/commandline.go | 10 +++ config/config.go | 6 ++ framework.go | 37 ++++++++ 10 files changed, 295 insertions(+), 64 deletions(-) create mode 100644 api/client/client.go create mode 100644 c2/cli/managed.go diff --git a/api/client/client.go b/api/client/client.go new file mode 100644 index 0000000..92787eb --- /dev/null +++ b/api/client/client.go @@ -0,0 +1,60 @@ +package client + +// exists to provide helper functions to interact with the api +import ( + "encoding/json" + "net/http" + + "github.com/vulncheck-oss/go-exploit/config" + "github.com/vulncheck-oss/go-exploit/c2" + "github.com/vulncheck-oss/go-exploit/c2/channel" + "github.com/vulncheck-oss/go-exploit/output" + "github.com/vulncheck-oss/go-exploit/protocol" + "github.com/vulncheck-oss/go-exploit/api/types" + httpapi "github.com/vulncheck-oss/go-exploit/api/http" +) + +var C2TypeMap = map[c2.Impl]string { + c2.SimpleShellServer: types.SimpleShellServer, + c2.SSLShellServer: types.SSLShellServer, +} + +func StartListener(conf *config.Config, serverChannel *channel.Channel) bool { + serverType, ok := C2TypeMap[conf.C2Type] + if !ok { + output.PrintfFrameworkError("Error creating API request to start listener, invalid ServerType provided.") + + return false + } + + req := httpapi.StartListenerRequest { + ServerType: serverType, + IPAddr: serverChannel.IPAddr, + Port: serverChannel.Port, + Timeout: serverChannel.Timeout, + HTTPAddr: serverChannel.HTTPAddr, + HTTPPort: serverChannel.HTTPPort, + } + + requestString, err := json.Marshal(req) + if err != nil { + output.PrintfFrameworkError("Failed to encode JSON for StartListener API request: %s", err) + + return false + } + + url := protocol.GenerateURL(conf.APIAddr, conf.APIPort, conf.SSL, "/startListener") + resp, body, ok := protocol.HTTPSendAndRecv("POST", url, string(requestString)) + if !ok { + return false + } + + if resp.StatusCode != http.StatusOK { + output.PrintfFrameworkDebug("Received failure status for StartListener API request: %d, message: %s", resp.StatusCode, body) + + return false + } + + output.PrintFrameworkStatus("API successfully created the listener") + return true +} diff --git a/api/http/handlers.go b/api/http/handlers.go index 40d22a2..ba1aa03 100644 --- a/api/http/handlers.go +++ b/api/http/handlers.go @@ -3,10 +3,8 @@ package http import ( "encoding/json" "net/http" - "strconv" "github.com/vulncheck-oss/go-exploit/api/listenermanager" - "github.com/vulncheck-oss/go-exploit/api/types" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/output" ) @@ -26,10 +24,20 @@ type GetListenersAPIResponse struct { Data []listenermanager.GetListenersResponse `json:"data"` } +type StartListenerRequest struct { + ServerType string `json:"servertype"` + IPAddr string `json:"ipaddr"` + Port int `json:"port"` + Timeout int `json:"timeout"` + HTTPAddr string `json:"httpaddr"` + HTTPPort int `json:"httpport"` +} + type StopListenerRequest struct { UUID string `json:"uuid"` } + func sendAPIResponse(w http.ResponseWriter, status string, errMessage string) { if status == "failure" { w.WriteHeader(http.StatusBadRequest) @@ -49,57 +57,30 @@ func startListener(w http.ResponseWriter, r *http.Request) { return } - var data map[string]string - - err := json.NewDecoder(r.Body).Decode(&data) - if err != nil { - sendAPIResponse(w, "failure", "Invalid JSON") - - return - } - - // Decision point: instead of decoding into a struct, doing it this way so it's easier to debug - serverType, ok := data[types.ServerType] - if !ok { - sendAPIResponse(w, "failure", "Missing serverType") - - return - } - - serverAddr, ok := data[types.ServerAddr] - if !ok { - sendAPIResponse(w, "failure", "Missing serverAddr") - - return - } + var request StartListenerRequest - serverPort, ok := data[types.ServerPort] - if !ok { - sendAPIResponse(w, "failure", "Missing serverPort") - - return - } - serverPortInt, err := strconv.Atoi(serverPort) + err := json.NewDecoder(r.Body).Decode(&request) if err != nil { - sendAPIResponse(w, "failure", "Invalid server port, could not parse as int") + sendAPIResponse(w, "failure", "Invalid request provided") return } - serverTimeout, ok := data[types.ServerTimeout] - if !ok { + if request.Timeout == 0 { output.PrintFrameworkWarn("No serverTimeout provided, using 30 instead") - serverTimeout = "30" + request.Timeout = 30 } - serverTimeoutInt, err := strconv.Atoi(serverTimeout) - if err != nil { - sendAPIResponse(w, "failure", "Invalid server timeout, could not parse as int") - return + serverChannel := channel.Channel{ + Timeout: request.Timeout, + HTTPAddr: request.HTTPAddr, + HTTPPort: request.HTTPPort, + IPAddr: request.IPAddr, + Port: request.Port, + Managed: true, } - serverChannel := channel.Channel{Port: serverPortInt, Timeout: serverTimeoutInt, HTTPAddr: serverAddr, HTTPPort: serverPortInt, IPAddr: serverAddr} - uuid, ok := listenermanager.GetInstance().CreateListener(serverType, serverChannel) + uuid, ok := listenermanager.GetInstance().StartListener(request.ServerType, serverChannel) if !ok { sendAPIResponse(w, "failure", "Failed to start listener") @@ -168,3 +149,13 @@ func stopListener(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, "success", "Successfully shutdown server") } + +func getStatus(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + sendAPIResponse(w, "failure", "Invalid request method") + + return + } + + sendAPIResponse(w, "success", "go-exploit-server-status: good") +} diff --git a/api/http/http.go b/api/http/http.go index 510b543..114fbca 100644 --- a/api/http/http.go +++ b/api/http/http.go @@ -88,6 +88,7 @@ func (server *Server) Run() { APIMux.HandleFunc("/startListener", startListener) APIMux.HandleFunc("/stopListener", stopListener) APIMux.HandleFunc("/getListeners", getListeners) + APIMux.HandleFunc("/status", getStatus) // start the server in a go routine, can kill it with the serverHandle wg.Add(1) diff --git a/api/listenermanager/listenermanager.go b/api/listenermanager/listenermanager.go index 836991f..1c0f751 100644 --- a/api/listenermanager/listenermanager.go +++ b/api/listenermanager/listenermanager.go @@ -29,7 +29,7 @@ type ListenerManager struct { } type GetListenersResponse struct { - Uuid string `json:"uuid"` + UUID string `json:"uuid"` Ipaddr string `json:"ipaddr"` Port int `json:"port"` Httpaddr string `json:"httpaddr"` @@ -67,7 +67,7 @@ func (manager *ListenerManager) GetListeners() ([]GetListenersResponse, bool) { for key, item := range manager.ServersMap { retArray = append(retArray, GetListenersResponse{ - Uuid: key, + UUID: key, Ipaddr: item.Channel().IPAddr, Port: item.Channel().Port, Httpaddr: item.Channel().HTTPAddr, @@ -80,7 +80,7 @@ func (manager *ListenerManager) GetListeners() ([]GetListenersResponse, bool) { return retArray, true } -func (manager *ListenerManager) CreateListener(serverType string, serverChan channel.Channel) (string, bool) { +func (manager *ListenerManager) StartListener(serverType string, serverChan channel.Channel) (string, bool) { var ok bool switch serverType { case types.SimpleShellServer: diff --git a/c2/channel/channel.go b/c2/channel/channel.go index 3e51a33..e8ab682 100644 --- a/c2/channel/channel.go +++ b/c2/channel/channel.go @@ -15,16 +15,17 @@ import ( ) type Channel struct { - IPAddr string - HTTPAddr string - Port int - HTTPPort int - Timeout int - IsClient bool - Shutdown *atomic.Bool - Sessions map[string]Session - Input io.Reader - Output io.Writer // Currently unused but figured we'd add it ahead of time + IPAddr string + HTTPAddr string + Port int + HTTPPort int + Timeout int + IsClient bool + Shutdown *atomic.Bool + Sessions map[string]Session + Input io.Reader + Output io.Writer // Currently unused but figured we'd add it ahead of time + Managed bool// for use by listener manager, to signal to listener to spawn new connection into cli.Managed instead of cli.Basic } type Session struct { diff --git a/c2/cli/managed.go b/c2/cli/managed.go new file mode 100644 index 0000000..fd73a4c --- /dev/null +++ b/c2/cli/managed.go @@ -0,0 +1,119 @@ +package cli + +import ( + "bufio" + "net" + "os" + "sync" + "testing" + "time" + "sync/atomic" + + "github.com/vulncheck-oss/go-exploit/c2/channel" + "github.com/vulncheck-oss/go-exploit/output" + "github.com/vulncheck-oss/go-exploit/protocol" +) + +var sessionEnded atomic.Bool + +// backgroundResponse handles the network connection reading for response data +func managedBackgroundResponse(ch *channel.Channel, wg *sync.WaitGroup, conn net.Conn, responseCh chan string) { + defer wg.Done() + responseBuffer := make([]byte, 1024) + for { + if ch.Shutdown.Load() || sessionEnded.Load() { + return + } + time.Sleep(10 * time.Millisecond) + + err := conn.SetReadDeadline(time.Now().Add(1 * time.Second)) + if err != nil { + output.PrintfFrameworkError("Error setting read deadline: %s, exiting.", err) + + return + } + + bytesRead, err := conn.Read(responseBuffer) + if err != nil && !os.IsTimeout(err) { + // things have gone sideways, but the command line won't know that + // until they attempt to execute a command and the socket fails. + // i think that's largely okay. + return + } + + if bytesRead > 0 { + // I think there is technically a race condition here where the socket + // could have move data to write, but the user has already called exit + // below. I that that's tolerable for now. + responseCh <- string(responseBuffer[:bytesRead]) + } + } +} + +// does most of what cli.Basic does but does not trigger the shutdowns of the associated server +func Managed(conn net.Conn, ch *channel.Channel) { + // Create channels for communication between goroutines. + responseCh := make(chan string) + sessionEnded.Store(false) + + // Use a WaitGroup to wait for goroutines to finish. + var wg sync.WaitGroup + + // Goroutine to read responses from the server. + wg.Add(1) + + // If running in the test context inherit the channel input setting, this will let us control the + // input of the shell programmatically. + if !testing.Testing() { + ch.Input = os.Stdin + } + go managedBackgroundResponse(ch, &wg, conn, responseCh) + + // Goroutine to handle responses and print them. + wg.Add(1) + go func(channel *channel.Channel) { + defer wg.Done() + for { + if channel.Shutdown.Load() || sessionEnded.Load() { + return + } + select { + case response := <-responseCh: + output.PrintShell(response) + default: + } + time.Sleep(10 * time.Millisecond) + } + }(ch) + + go func(channel *channel.Channel) { + // no waitgroup for this one because blocking IO, but this should not matter + // since we are intentionally not trying to be a multi-implant C2 framework. + // There still remains the issue that you would need to hit enter to find out + // that the socket is dead but at least we can stop Basic() regardless of this fact. + // This issue of unblocking stdin is discussed at length here https://github.com/golang/go/issues/24842 + for { + reader := bufio.NewReader(ch.Input) + command, _ := reader.ReadString('\n') + if channel.Shutdown.Load() { + break + } + if command == "exit\n" { + sessionEnded.Store(true) + + break + } + ok := protocol.TCPWrite(conn, []byte(command)) + if !ok { + sessionEnded.Store(true) + + break + } + time.Sleep(10 * time.Millisecond) + } + }(ch) + + // wait until the go routines are clean up + wg.Wait() + close(responseCh) +} diff --git a/c2/simpleshell/simpleshellserver.go b/c2/simpleshell/simpleshellserver.go index c974bd0..c968caa 100644 --- a/c2/simpleshell/simpleshellserver.go +++ b/c2/simpleshell/simpleshellserver.go @@ -134,18 +134,24 @@ func (shellServer *Server) Run(timeout int) { } func handleSimpleConn(conn net.Conn, cliLock *sync.Mutex, remoteAddr net.Addr, ch *channel.Channel) { - // connections will stack up here. Currently that will mean a race - // to the next connection but we can add in attacker handling of - // connections latter - cliLock.Lock() - defer cliLock.Unlock() - - // close the connection when the shell is complete - defer conn.Close() + if !ch.Managed { + // connections will stack up here. Currently that will mean a race + // to the next connection but we can add in attacker handling of + // connections latter + cliLock.Lock() + defer cliLock.Unlock() + + // close the connection when the shell is complete + defer conn.Close() + } // Only complete the full session handshake once if !ch.Shutdown.Load() { output.PrintfFrameworkStatus("Active shell from %v", remoteAddr) - cli.Basic(conn, ch) + if !ch.Managed { + cli.Basic(conn, ch) + } else { + cli.Managed(conn, ch) + } } } diff --git a/cli/commandline.go b/cli/commandline.go index 258ea06..dafa2a1 100644 --- a/cli/commandline.go +++ b/cli/commandline.go @@ -404,6 +404,13 @@ func exploitFunctionality(conf *config.Config) { flag.BoolVar(&conf.DoExploit, "e", false, "Exploit the target") } +// command line flags that control API server configuration +func apiFlags(conf *config.Config) { + flag.BoolVar(&conf.UseAPI, "useapi", false, "Use API server. Will attempt to connect to 127.0.0.1:2828 if -apiport or -apiaddr are not provided") + flag.IntVar(&conf.APIPort, "apiport", 2828, "Specify the port of the running API server") + flag.StringVar(&conf.APIAddr, "apiaddr", "127.0.0.1", "Specify the ip address or hostname of the running API server") +} + // command line flags that control ssl communication with the target. func sslFlags(conf *config.Config) { flag.BoolVar(&conf.SSL, "s", false, "Use https to communicate with the target") @@ -547,6 +554,7 @@ func CodeExecutionCmdLineParse(conf *config.Config) bool { localHostFlags(conf) exploitFunctionality(conf) sslFlags(conf) + apiFlags(conf) c2Flags(&c2Selection, conf) detailsFlag := flag.Bool("details", false, "Print the implementation details for this exploit") @@ -612,6 +620,7 @@ func InformationDisclosureCmdLineParse(conf *config.Config) bool { localHostFlags(conf) exploitFunctionality(conf) sslFlags(conf) + apiFlags(conf) detailsFlag := flag.Bool("details", false, "Print the implementation details for this exploit") flag.Usage = func() { @@ -654,6 +663,7 @@ func WebShellCmdLineParse(conf *config.Config) bool { localHostFlags(conf) exploitFunctionality(conf) sslFlags(conf) + apiFlags(conf) detailsFlag := flag.Bool("details", false, "Print the implementation details for this exploit") flag.Usage = func() { diff --git a/config/config.go b/config/config.go index 3559f1b..4b805ae 100644 --- a/config/config.go +++ b/config/config.go @@ -114,6 +114,12 @@ type Config struct { FileTemplateData string // File format exploit output FileFormatFilePath string + // Disables use of the API for server spawning + UseAPI bool + // Port of the currently running API server + APIPort int + // IP ADDR of the currently running API server + APIAddr string } // Convert ExploitType to String. diff --git a/framework.go b/framework.go index 51f83f3..1115d3a 100644 --- a/framework.go +++ b/framework.go @@ -70,6 +70,7 @@ import ( "sync" "sync/atomic" "time" + "net/http" "github.com/Masterminds/semver" "github.com/vulncheck-oss/go-exploit/c2" @@ -79,6 +80,7 @@ import ( "github.com/vulncheck-oss/go-exploit/db" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" + "github.com/vulncheck-oss/go-exploit/api/client" ) // The return type for CheckVersion(). @@ -307,6 +309,13 @@ func startC2Server(conf *config.Config) bool { IsClient: false, Shutdown: &shutdown, } + + if isAPIServerPresent(conf){ + output.PrintFrameworkStatus("API server detected, will spawn or use listener there X") + + return client.StartListener(conf, c2channel) + } + // Handle the signal interrupt channel. If the signal is triggered, then trigger the done // channel which will clean up the server and close cleanly. go func(sigint <-chan os.Signal, channel *channel.Channel) { @@ -526,3 +535,31 @@ func RunProgram(sploit Exploit, conf *config.Config) { } } } + +func isAPIServerPresent(conf *config.Config) bool { + if !conf.UseAPI { + output.PrintFrameworkDebug("-useAPI flag not set, skipping API server check") + + return false + } + if conf.APIAddr == "" { + conf.APIAddr = "127.0.0.1" // Default API Address + } + if conf.APIPort == 0 { + conf.APIPort = 2828 // Default API Port + } + + url := protocol.GenerateURL(conf.APIAddr, conf.APIPort, conf.SSL, "/status") + resp, body, ok := protocol.HTTPSendAndRecv("GET", url, "") + if !ok { + return false + } + + if resp.StatusCode != http.StatusOK { + output.PrintfFrameworkDebug("Received invalid status code from possible API server: %d", resp.StatusCode) + + return false + } + + return strings.Contains(body, "go-exploit-server-status: good") +} From 7be9a078dc18e082b356e2ea626f208528a9c3ce Mon Sep 17 00:00:00 2001 From: lobsterjerusalem Date: Wed, 21 May 2025 19:56:43 -0600 Subject: [PATCH 10/11] added sessions command, incomplete push --- api/testapi/test.go | 7 ++- c2/channel/channel.go | 4 ++ c2/cli/managed.go | 36 ++++++++++--- c2/simpleshell/simpleshellserver.go | 28 +++++----- cmd/cli/cmd.go | 78 ++++++++++++++++++++++++++++ cmd/commands/sessions/sessions.go | 80 +++++++++++++++++++++++++++++ framework.go | 2 +- go.mod | 5 ++ go.sum | 32 ++++++++++++ 9 files changed, 247 insertions(+), 25 deletions(-) create mode 100644 cmd/cli/cmd.go create mode 100644 cmd/commands/sessions/sessions.go diff --git a/api/testapi/test.go b/api/testapi/test.go index 55df7c4..6cd081a 100644 --- a/api/testapi/test.go +++ b/api/testapi/test.go @@ -1,10 +1,15 @@ package main import ( + "github.com/vulncheck-oss/go-exploit/c2/cli" "github.com/vulncheck-oss/go-exploit/api/http" + "github.com/vulncheck-oss/go-exploit/api/listenermanager" ) func main() { http.GetInstance().Init("127.0.0.1", 2828) - http.GetInstance().Run() + go http.GetInstance().Run() + lManager := listenermanager.GetInstance() + for { + } } diff --git a/c2/channel/channel.go b/c2/channel/channel.go index e8ab682..54ecde2 100644 --- a/c2/channel/channel.go +++ b/c2/channel/channel.go @@ -34,6 +34,10 @@ type Session struct { conn *net.Conn } +func (session *Session) Conn() *net.Conn { + return session.conn +} + // HasSessions checks if a channel has any tracked sessions. This can be used to lookup if a C2 // successfully received callbacks: // diff --git a/c2/cli/managed.go b/c2/cli/managed.go index fd73a4c..94a5b5e 100644 --- a/c2/cli/managed.go +++ b/c2/cli/managed.go @@ -1,22 +1,33 @@ package cli import ( - "bufio" + _"bufio" "net" "os" "sync" "testing" "time" "sync/atomic" + "strings" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" + + + "github.com/confluentinc/go-prompt" ) +func shellCompleter(d prompt.Document) []prompt.Suggest { + s := []prompt.Suggest{ + {Text: "exit", Description: "Exit this session"}, + } + + return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true) +} var sessionEnded atomic.Bool -// backgroundResponse handles the network connection reading for response data +// backgroundResponse handles the network connection reading for response data. func managedBackgroundResponse(ch *channel.Channel, wg *sync.WaitGroup, conn net.Conn, responseCh chan string) { defer wg.Done() responseBuffer := make([]byte, 1024) @@ -42,6 +53,7 @@ func managedBackgroundResponse(ch *channel.Channel, wg *sync.WaitGroup, conn net } if bytesRead > 0 { + output.PrintfFrameworkStatus("Read %d bytes from client", bytesRead) // I think there is technically a race condition here where the socket // could have move data to write, but the user has already called exit // below. I that that's tolerable for now. @@ -93,22 +105,32 @@ func Managed(conn net.Conn, ch *channel.Channel) { // that the socket is dead but at least we can stop Basic() regardless of this fact. // This issue of unblocking stdin is discussed at length here https://github.com/golang/go/issues/24842 for { - reader := bufio.NewReader(ch.Input) - command, _ := reader.ReadString('\n') - if channel.Shutdown.Load() { - break + + + command := strings.TrimSpace(prompt.Input("$> ", shellCompleter)) + //reader := bufio.NewReader(ch.Input) + //command, _ := reader.ReadString('\n') + //if channel.Shutdown.Load() { + //break + //} + + if command == "" { + continue } - if command == "exit\n" { + if command == "exit\n" || command == "exit" { sessionEnded.Store(true) break } + output.PrintfFrameworkStatus("Sending %s to client", command) ok := protocol.TCPWrite(conn, []byte(command)) if !ok { + output.PrintFrameworkError("Failed to send") sessionEnded.Store(true) break } + output.PrintFrameworkStatus("Sent successfully") time.Sleep(10 * time.Millisecond) } }(ch) diff --git a/c2/simpleshell/simpleshellserver.go b/c2/simpleshell/simpleshellserver.go index c968caa..175bac6 100644 --- a/c2/simpleshell/simpleshellserver.go +++ b/c2/simpleshell/simpleshellserver.go @@ -129,29 +129,25 @@ func (shellServer *Server) Run(timeout int) { // Add the session for tracking first, so it can be cleaned up. shellServer.Channel().AddSession(&client, client.RemoteAddr().String()) - go handleSimpleConn(client, &cliLock, client.RemoteAddr(), shellServer.channel) + if !shellServer.channel.Managed { + go handleSimpleConn(client, &cliLock, client.RemoteAddr(), shellServer.channel) + } } } func handleSimpleConn(conn net.Conn, cliLock *sync.Mutex, remoteAddr net.Addr, ch *channel.Channel) { - if !ch.Managed { - // connections will stack up here. Currently that will mean a race - // to the next connection but we can add in attacker handling of - // connections latter - cliLock.Lock() - defer cliLock.Unlock() - - // close the connection when the shell is complete - defer conn.Close() - } + // connections will stack up here. Currently that will mean a race + // to the next connection but we can add in attacker handling of + // connections latter + cliLock.Lock() + defer cliLock.Unlock() + + // close the connection when the shell is complete + defer conn.Close() // Only complete the full session handshake once if !ch.Shutdown.Load() { output.PrintfFrameworkStatus("Active shell from %v", remoteAddr) - if !ch.Managed { - cli.Basic(conn, ch) - } else { - cli.Managed(conn, ch) - } + cli.Basic(conn, ch) } } diff --git a/cmd/cli/cmd.go b/cmd/cli/cmd.go new file mode 100644 index 0000000..5b1b371 --- /dev/null +++ b/cmd/cli/cmd.go @@ -0,0 +1,78 @@ +package main + +import ( + "os/exec" + "strings" + "os" + "fmt" + + "github.com/vulncheck-oss/go-exploit/cmd/commands/search" + "github.com/vulncheck-oss/go-exploit/cmd/commands/use" + "github.com/vulncheck-oss/go-exploit/cmd/commands/sessions" + "github.com/vulncheck-oss/go-exploit/api/http" + "github.com/confluentinc/go-prompt" +) + +var ps1 = "> " + +func mainCommandDispatch(input string) { + input = strings.TrimSpace(input) + parts := strings.Split(input, " ") + + if len(parts) == 0 { + return + } + + switch parts[0] { + case "sessions": + sessions.CallSessions(parts) + case "search": + search.CallSearch(parts) + case "use": + use.CallUse(parts) + default: + if parts[0] == "" { + return + } + fmt.Println("Unknown command:", parts[0]) + } +} + +func mainCompleter(d prompt.Document) []prompt.Suggest { + s := []prompt.Suggest{ + {Text: "sessions", Description: sessions.Description}, + {Text: "use", Description: use.Description}, + {Text: "exit", Description: "Exit this interface"}, + } + + return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true) +} + +// ensures the terminal goes back to a working state when finished. +func handleExit() { + rawModeOff := exec.Command("/bin/stty", "-raw", "echo") + rawModeOff.Stdin = os.Stdin + _ = rawModeOff.Run() + _ = rawModeOff.Wait() +} + +func cliExit() { + fmt.Println("exiting...") + handleExit() + os.Exit(0) +} + +func main() { + http.GetInstance().Init("127.0.0.1", 2828) + go http.GetInstance().Run() + + fmt.Println("VULNCHECK GO-EXPLOIT CLI") + for { + input := prompt.Input(ps1, mainCompleter) + if input == "exit" { + break + } + mainCommandDispatch(input) + } + cliExit() +} diff --git a/cmd/commands/sessions/sessions.go b/cmd/commands/sessions/sessions.go new file mode 100644 index 0000000..ac2ef57 --- /dev/null +++ b/cmd/commands/sessions/sessions.go @@ -0,0 +1,80 @@ +package sessions + +import ( + "fmt" + "github.com/vulncheck-oss/go-exploit/c2/cli" + "github.com/vulncheck-oss/go-exploit/api/listenermanager" + +) + +var usage = `Usage: +sessions -l - list all active sessions with their ids +sessions -i - interact with session +sessions -k - kill session +` + +var Description = "Manage and interact with active sessions" + + +func interact(args []string) { + if len(args) != 3 { + fmt.Println("ERROR: Invalid number of args for session interact command") + fmt.Println(usage) + + return + } + + id := args[2] + + conn, channel, ok := listenermanager.GetSessionByID(id) + if !ok { + fmt.Println("ERROR: Failed to get session from manager using provided id") + + return + } + fmt.Println("Session retrieved, dropping into shell...") + // cli.Managed(*conn, channel) + cli.Basic(*conn, channel) +} + +func kill(args []string){ + if len(args) != 3 { + fmt.Println("ERROR: Invalid number of args for session kill command") + fmt.Println(usage) + + return + } + + // id := args[2] +} + +func list(){ + sessionMap := listenermanager.GetSessions() + if len(sessionMap) == 0 { + fmt.Println("No sessions available") + + return + } + for id, session := range sessionMap { + fmt.Printf("%s - %s - %s", id, session.RemoteAddr, session.ConnectionTime) + } +} + +func CallSessions(args []string){ + if len(args) < 2 { + fmt.Println("ERROR: Invalid number of args") + fmt.Println(usage) + + return + } + switch args[1] { + case "-l": + list() + case "-k": + kill(args) + case "-i": + interact(args) + default: + fmt.Println(usage) + } +} diff --git a/framework.go b/framework.go index 1115d3a..4cb2fe9 100644 --- a/framework.go +++ b/framework.go @@ -311,7 +311,7 @@ func startC2Server(conf *config.Config) bool { } if isAPIServerPresent(conf){ - output.PrintFrameworkStatus("API server detected, will spawn or use listener there X") + output.PrintFrameworkStatus("API server detected, will spawn or use listener there") return client.StartListener(conf, c2channel) } diff --git a/go.mod b/go.mod index 7b3e23d..eb206e0 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.24.1 require ( github.com/Masterminds/semver v1.5.0 + github.com/confluentinc/go-prompt v1.0.0 github.com/google/uuid v1.6.0 github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 github.com/vjeantet/ldapserver v1.0.2-0.20240305064909-a417792e2906 @@ -15,8 +16,12 @@ require ( require ( github.com/dustin/go-humanize v1.0.1 // indirect + github.com/mattn/go-colorable v0.1.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mattn/go-tty v0.0.3 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/pkg/term v1.2.0-beta.2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/sys v0.33.0 // indirect diff --git a/go.sum b/go.sum index c3320b9..7c9c42a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/confluentinc/go-prompt v1.0.0 h1:sxSWhXIkgMYRhirGoMcfr5okSlBjDeYxF/wcpcoWs/w= +github.com/confluentinc/go-prompt v1.0.0/go.mod h1:4/tf63YzhSsiXnsKosCmv1H0ivpXVezZHaMpSVB+DXw= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= @@ -9,12 +13,29 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0= github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 h1:z9RDOBcFcf3f2hSfKuoM3/FmJpt8M+w0fOy4wKneBmc= github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= +github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= +github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/vjeantet/ldapserver v1.0.2-0.20240305064909-a417792e2906 h1:qHFp1iRg6qE8xYel3bQT9x70pyxsdPLbJnM40HG3Oig= github.com/vjeantet/ldapserver v1.0.2-0.20240305064909-a417792e2906/go.mod h1:YvUqhu5vYhmbcLReMLrm/Tq3S7Yj43kSVFvvol6Lh6k= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= @@ -25,8 +46,15 @@ golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= @@ -34,6 +62,8 @@ golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= @@ -58,3 +88,5 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +pgregory.net/rapid v0.5.5 h1:jkgx1TjbQPD/feRoK+S/mXw9e1uj6WilpHrXJowi6oA= +pgregory.net/rapid v0.5.5/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= From 067e7a9f4d24c32c7ff3e7f5d284fb1f96f9d27b Mon Sep 17 00:00:00 2001 From: lobsterjerusalem Date: Wed, 21 May 2025 20:08:53 -0600 Subject: [PATCH 11/11] sessions functioning, push still excludes deprecated deps, will remove on next push --- c2/cli/managed.go | 34 ++++++++----------------------- cmd/cli/cmd.go | 10 ++++++++- cmd/commands/sessions/sessions.go | 3 +-- 3 files changed, 18 insertions(+), 29 deletions(-) diff --git a/c2/cli/managed.go b/c2/cli/managed.go index 94a5b5e..5f10279 100644 --- a/c2/cli/managed.go +++ b/c2/cli/managed.go @@ -1,29 +1,19 @@ package cli import ( - _"bufio" + "bufio" "net" + "strings" "os" "sync" "testing" "time" "sync/atomic" - "strings" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/output" "github.com/vulncheck-oss/go-exploit/protocol" - - - "github.com/confluentinc/go-prompt" ) -func shellCompleter(d prompt.Document) []prompt.Suggest { - s := []prompt.Suggest{ - {Text: "exit", Description: "Exit this session"}, - } - - return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true) -} var sessionEnded atomic.Bool @@ -53,7 +43,6 @@ func managedBackgroundResponse(ch *channel.Channel, wg *sync.WaitGroup, conn net } if bytesRead > 0 { - output.PrintfFrameworkStatus("Read %d bytes from client", bytesRead) // I think there is technically a race condition here where the socket // could have move data to write, but the user has already called exit // below. I that that's tolerable for now. @@ -105,16 +94,12 @@ func Managed(conn net.Conn, ch *channel.Channel) { // that the socket is dead but at least we can stop Basic() regardless of this fact. // This issue of unblocking stdin is discussed at length here https://github.com/golang/go/issues/24842 for { - - - command := strings.TrimSpace(prompt.Input("$> ", shellCompleter)) - //reader := bufio.NewReader(ch.Input) - //command, _ := reader.ReadString('\n') - //if channel.Shutdown.Load() { - //break - //} - - if command == "" { + reader := bufio.NewReader(ch.Input) + command, _ := reader.ReadString('\n') + if channel.Shutdown.Load() { + break + } + if strings.TrimSpace(command) == "" { continue } if command == "exit\n" || command == "exit" { @@ -122,15 +107,12 @@ func Managed(conn net.Conn, ch *channel.Channel) { break } - output.PrintfFrameworkStatus("Sending %s to client", command) ok := protocol.TCPWrite(conn, []byte(command)) if !ok { - output.PrintFrameworkError("Failed to send") sessionEnded.Store(true) break } - output.PrintFrameworkStatus("Sent successfully") time.Sleep(10 * time.Millisecond) } }(ch) diff --git a/cmd/cli/cmd.go b/cmd/cli/cmd.go index 5b1b371..14cfad8 100644 --- a/cmd/cli/cmd.go +++ b/cmd/cli/cmd.go @@ -2,6 +2,7 @@ package main import ( "os/exec" + "bufio" "strings" "os" "fmt" @@ -68,7 +69,14 @@ func main() { fmt.Println("VULNCHECK GO-EXPLOIT CLI") for { - input := prompt.Input(ps1, mainCompleter) + // input := prompt.Input(ps1, mainCompleter) // not working, weirdly messing up all I/O, or so I suspect + reader := bufio.NewReader(os.Stdin) + + input, err := reader.ReadString('\n') + if err != nil { + fmt.Println("Error reading input:", err) + return + } if input == "exit" { break } diff --git a/cmd/commands/sessions/sessions.go b/cmd/commands/sessions/sessions.go index ac2ef57..2eeade3 100644 --- a/cmd/commands/sessions/sessions.go +++ b/cmd/commands/sessions/sessions.go @@ -33,8 +33,7 @@ func interact(args []string) { return } fmt.Println("Session retrieved, dropping into shell...") - // cli.Managed(*conn, channel) - cli.Basic(*conn, channel) + cli.Managed(*conn, channel) } func kill(args []string){