Skip to content

HTTP API #382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
@@ -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
}
161 changes: 161 additions & 0 deletions api/http/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package http

import (
"encoding/json"
"net/http"

"github.com/vulncheck-oss/go-exploit/api/listenermanager"
"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"`
}

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"`
}

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)
}
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
}

var request StartListenerRequest

err := json.NewDecoder(r.Body).Decode(&request)
if err != nil {
sendAPIResponse(w, "failure", "Invalid request provided")

return
}

if request.Timeout == 0 {
output.PrintFrameworkWarn("No serverTimeout provided, using 30 instead")
request.Timeout = 30
}

serverChannel := channel.Channel{
Timeout: request.Timeout,
HTTPAddr: request.HTTPAddr,
HTTPPort: request.HTTPPort,
IPAddr: request.IPAddr,
Port: request.Port,
Managed: true,
}

uuid, ok := listenermanager.GetInstance().StartListener(request.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) {
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")
}

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")
}
144 changes: 144 additions & 0 deletions api/http/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package http

import (
"crypto/tls"
"fmt"
"net/http"
"sync"
"sync/atomic"
"time"

"github.com/vulncheck-oss/go-exploit/encryption"
"github.com/vulncheck-oss/go-exploit/output"
)

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
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("/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)
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")
//}
//}
Loading
Loading