From a15f5ea836ce4ee34d8b6fceac87c142858dda59 Mon Sep 17 00:00:00 2001 From: Tim Allclair Date: Mon, 6 Jul 2020 17:02:43 -0700 Subject: [PATCH 1/5] Add optional code to netexec echo endpoint --- test/images/agnhost/README.md | 2 +- test/images/agnhost/netexec/netexec.go | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/test/images/agnhost/README.md b/test/images/agnhost/README.md index f1193c2b57a..6d1f747df18 100644 --- a/test/images/agnhost/README.md +++ b/test/images/agnhost/README.md @@ -393,7 +393,7 @@ Starts a HTTP(S) server on given port with the following endpoints: - `protocol`: The protocol which will be used when making the request. Default value: `http`. Acceptable values: `http`, `udp`, `sctp`. - `tries`: The number of times the request will be performed. Default value: `1`. -- `/echo`: Returns the given `msg` (`/echo?msg=echoed_msg`) +- `/echo`: Returns the given `msg` (`/echo?msg=echoed_msg`), with the optional status `code`. - `/exit`: Closes the server with the given code and graceful shutdown. The endpoint's parameters are: - `code`: The exit code for the process. Default value: 0. Allows an integer [0-127]. diff --git a/test/images/agnhost/netexec/netexec.go b/test/images/agnhost/netexec/netexec.go index 0bebcfc8f3a..92562bef9f5 100644 --- a/test/images/agnhost/netexec/netexec.go +++ b/test/images/agnhost/netexec/netexec.go @@ -69,7 +69,7 @@ var CmdNetexec = &cobra.Command{ - "protocol": The protocol which will be used when making the request. Default value: "http". Acceptable values: "http", "udp", "sctp". - "tries": The number of times the request will be performed. Default value: "1". -- "/echo": Returns the given "msg" ("/echo?msg=echoed_msg") +- "/echo": Returns the given "msg" ("/echo?msg=echoed_msg"), with the optional status "code". - "/exit": Closes the server with the given code and graceful shutdown. The endpoint's parameters are: - "code": The exit code for the process. Default value: 0. Allows an integer [0-127]. @@ -166,6 +166,7 @@ func addRoutes(exitCh chan shutdownRequest) { } func startServer(server *http.Server, exitCh chan shutdownRequest, fn func() error) { + log.Printf("Started HTTP server on port %d", httpPort) go func() { re := <-exitCh ctx, cancelFn := context.WithTimeout(context.Background(), re.timeout) @@ -190,8 +191,18 @@ func rootHandler(w http.ResponseWriter, r *http.Request) { } func echoHandler(w http.ResponseWriter, r *http.Request) { - log.Printf("GET /echo?msg=%s", r.FormValue("msg")) - fmt.Fprintf(w, "%s", r.FormValue("msg")) + msg := r.FormValue("msg") + codeString := r.FormValue("code") + log.Printf("GET /echo?msg=%s&code=%s", msg, codeString) + if codeString != "" { + code, err := strconv.Atoi(codeString) + if err != nil && codeString != "" { + fmt.Fprintf(w, "argument 'code' must be an integer or empty, got %q\n", codeString) + return + } + w.WriteHeader(code) + } + fmt.Fprintf(w, "%s", msg) } func clientIPHandler(w http.ResponseWriter, r *http.Request) { @@ -497,7 +508,7 @@ func startUDPServer(udpPort int) { defer serverConn.Close() buf := make([]byte, 2048) - log.Printf("Started UDP server") + log.Printf("Started UDP server on port %d", udpPort) // Start responding to readiness probes. serverReady.set(true) defer func() { From 91563602343123f50fcff6dc24381c08e67e9661 Mon Sep 17 00:00:00 2001 From: Tim Allclair Date: Mon, 6 Jul 2020 17:17:10 -0700 Subject: [PATCH 2/5] Add a redirect handler to netexec --- test/images/agnhost/README.md | 2 ++ test/images/agnhost/netexec/netexec.go | 23 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/test/images/agnhost/README.md b/test/images/agnhost/README.md index 6d1f747df18..fefcbd18dfb 100644 --- a/test/images/agnhost/README.md +++ b/test/images/agnhost/README.md @@ -407,6 +407,8 @@ Starts a HTTP(S) server on given port with the following endpoints: it exited. - `/hostname`: Returns the server's hostname. - `/hostName`: Returns the server's hostname. +- `/redirect`: Returns a redirect response to the given `location`, with the optional status `code` + (`/redirect?location=/echo%3Fmsg=foobar&code=307`). - `/shell`: Executes the given `shellCommand` or `cmd` (`/shell?cmd=some-command`) and returns a JSON containing the fields `output` (command's output) and `error` (command's error message). Returns `200 OK` if the command succeeded, `417 Expectation Failed` if not. diff --git a/test/images/agnhost/netexec/netexec.go b/test/images/agnhost/netexec/netexec.go index 92562bef9f5..0c047266103 100644 --- a/test/images/agnhost/netexec/netexec.go +++ b/test/images/agnhost/netexec/netexec.go @@ -83,6 +83,8 @@ var CmdNetexec = &cobra.Command{ it exited. - "/hostname": Returns the server's hostname. - "/hostName": Returns the server's hostname. +- "/redirect": Returns a redirect response to the given "location", with the optional status "code" + ("/redirect?location=/echo%3Fmsg=foobar&code=307"). - "/shell": Executes the given "shellCommand" or "cmd" ("/shell?cmd=some-command") and returns a JSON containing the fields "output" (command's output) and "error" (command's error message). Returns "200 OK" if the command succeeded, "417 Expectation Failed" if not. @@ -153,13 +155,14 @@ func main(cmd *cobra.Command, args []string) { func addRoutes(exitCh chan shutdownRequest) { http.HandleFunc("/", rootHandler) http.HandleFunc("/clientip", clientIPHandler) + http.HandleFunc("/dial", dialHandler) http.HandleFunc("/echo", echoHandler) http.HandleFunc("/exit", func(w http.ResponseWriter, req *http.Request) { exitHandler(w, req, exitCh) }) + http.HandleFunc("/healthz", healthzHandler) http.HandleFunc("/hostname", hostnameHandler) + http.HandleFunc("/redirect", redirectHandler) http.HandleFunc("/shell", shellHandler) http.HandleFunc("/upload", uploadHandler) - http.HandleFunc("/dial", dialHandler) - http.HandleFunc("/healthz", healthzHandler) // older handlers http.HandleFunc("/hostName", hostNameHandler) http.HandleFunc("/shutdown", shutdownHandler) @@ -499,6 +502,22 @@ func hostNameHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, getHostName()) } +func redirectHandler(w http.ResponseWriter, r *http.Request) { + location := r.FormValue("location") + codeString := r.FormValue("code") + log.Printf("%s /redirect?msg=%s&code=%s", r.Method, location, codeString) + code := http.StatusFound + if codeString != "" { + var err error + code, err = strconv.Atoi(codeString) + if err != nil && codeString != "" { + fmt.Fprintf(w, "argument 'code' must be an integer or empty, got %q\n", codeString) + return + } + } + http.Redirect(w, r, location, code) +} + // udp server supports the hostName, echo and clientIP commands. func startUDPServer(udpPort int) { serverAddress, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", udpPort)) From 700006f2d9fda0e034b5f5f1d4da22fe7702745e Mon Sep 17 00:00:00 2001 From: Tim Allclair Date: Mon, 6 Jul 2020 18:26:22 -0700 Subject: [PATCH 3/5] Add override option to netexec --- test/images/agnhost/netexec/netexec.go | 58 ++++++++++++++++---------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/test/images/agnhost/netexec/netexec.go b/test/images/agnhost/netexec/netexec.go index 0c047266103..af05011c330 100644 --- a/test/images/agnhost/netexec/netexec.go +++ b/test/images/agnhost/netexec/netexec.go @@ -40,13 +40,14 @@ import ( ) var ( - httpPort = 8080 - udpPort = 8081 - sctpPort = -1 - shellPath = "/bin/sh" - serverReady = &atomicBool{0} - certFile = "" - privKeyFile = "" + httpPort = 8080 + udpPort = 8081 + sctpPort = -1 + shellPath = "/bin/sh" + serverReady = &atomicBool{0} + certFile = "" + privKeyFile = "" + httpOverride = "" ) // CmdNetexec is used by agnhost Cobra. @@ -114,6 +115,7 @@ func init() { "File containing an x509 private key matching --tls-cert-file") CmdNetexec.Flags().IntVar(&udpPort, "udp-port", 8081, "UDP Listen Port") CmdNetexec.Flags().IntVar(&sctpPort, "sctp-port", -1, "SCTP Listen Port") + CmdNetexec.Flags().StringVar(&httpOverride, "http-override", "", "Override the HTTP handler to always respond as if it were a GET with this path & params") } // atomicBool uses load/store operations on an int32 to simulate an atomic boolean. @@ -137,7 +139,21 @@ func (a *atomicBool) get() bool { func main(cmd *cobra.Command, args []string) { exitCh := make(chan shutdownRequest) - addRoutes(exitCh) + if httpOverride != "" { + mux := http.NewServeMux() + addRoutes(mux, exitCh) + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + overrideReq, err := http.NewRequestWithContext(r.Context(), "GET", httpOverride, nil) + if err != nil { + http.Error(w, fmt.Sprintf("override request failed: %v", err), http.StatusInternalServerError) + return + } + mux.ServeHTTP(w, overrideReq) + }) + } else { + addRoutes(http.DefaultServeMux, exitCh) + } go startUDPServer(udpPort) if sctpPort != -1 { @@ -152,20 +168,20 @@ func main(cmd *cobra.Command, args []string) { } } -func addRoutes(exitCh chan shutdownRequest) { - http.HandleFunc("/", rootHandler) - http.HandleFunc("/clientip", clientIPHandler) - http.HandleFunc("/dial", dialHandler) - http.HandleFunc("/echo", echoHandler) - http.HandleFunc("/exit", func(w http.ResponseWriter, req *http.Request) { exitHandler(w, req, exitCh) }) - http.HandleFunc("/healthz", healthzHandler) - http.HandleFunc("/hostname", hostnameHandler) - http.HandleFunc("/redirect", redirectHandler) - http.HandleFunc("/shell", shellHandler) - http.HandleFunc("/upload", uploadHandler) +func addRoutes(mux *http.ServeMux, exitCh chan shutdownRequest) { + mux.HandleFunc("/", rootHandler) + mux.HandleFunc("/clientip", clientIPHandler) + mux.HandleFunc("/dial", dialHandler) + mux.HandleFunc("/echo", echoHandler) + mux.HandleFunc("/exit", func(w http.ResponseWriter, req *http.Request) { exitHandler(w, req, exitCh) }) + mux.HandleFunc("/healthz", healthzHandler) + mux.HandleFunc("/hostname", hostnameHandler) + mux.HandleFunc("/redirect", redirectHandler) + mux.HandleFunc("/shell", shellHandler) + mux.HandleFunc("/upload", uploadHandler) // older handlers - http.HandleFunc("/hostName", hostNameHandler) - http.HandleFunc("/shutdown", shutdownHandler) + mux.HandleFunc("/hostName", hostNameHandler) + mux.HandleFunc("/shutdown", shutdownHandler) } func startServer(server *http.Server, exitCh chan shutdownRequest, fn func() error) { From 354cb6ca91c09581745628da0084b04cced3b813 Mon Sep 17 00:00:00 2001 From: Tim Allclair Date: Mon, 6 Jul 2020 18:26:44 -0700 Subject: [PATCH 4/5] Bump agnhost version --- test/images/agnhost/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/images/agnhost/VERSION b/test/images/agnhost/VERSION index fd6915cc46b..8bd2249aca9 100644 --- a/test/images/agnhost/VERSION +++ b/test/images/agnhost/VERSION @@ -1 +1 @@ -2.24 +2.25 From c05a350b4f16bbf1a709b82a97584efd153e6a8e Mon Sep 17 00:00:00 2001 From: Tim Allclair Date: Thu, 16 Jul 2020 10:26:01 -0700 Subject: [PATCH 5/5] Update usage information for --http-override --- test/images/agnhost/README.md | 3 +++ test/images/agnhost/netexec/netexec.go | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/test/images/agnhost/README.md b/test/images/agnhost/README.md index fefcbd18dfb..f3fd8252a2d 100644 --- a/test/images/agnhost/README.md +++ b/test/images/agnhost/README.md @@ -421,6 +421,9 @@ If `--tls-cert-file` is added (ideally in conjunction with `--tls-private-key-fi will be upgraded to HTTPS. The image has default, `localhost`-based cert/privkey files at `/localhost.crt` and `/localhost.key` (see: [`porter` subcommand](#porter)) +If `--http-override` is set, the HTTP(S) server will always serve the override path & options, +ignoring the request URL. + It will also start a UDP server on the indicated UDP port that responds to the following commands: - `hostname`: Returns the server's hostname diff --git a/test/images/agnhost/netexec/netexec.go b/test/images/agnhost/netexec/netexec.go index af05011c330..68cb8009f79 100644 --- a/test/images/agnhost/netexec/netexec.go +++ b/test/images/agnhost/netexec/netexec.go @@ -94,6 +94,13 @@ var CmdNetexec = &cobra.Command{ Returns a JSON with the fields "output" (containing the file's name on the server) and "error" containing any potential server side errors. +If "--tls-cert-file" is added (ideally in conjunction with "--tls-private-key-file", the HTTP server +will be upgraded to HTTPS. The image has default, "localhost"-based cert/privkey files at +"/localhost.crt" and "/localhost.key" (see: "porter" subcommand) + +If "--http-override" is set, the HTTP(S) server will always serve the override path & options, +ignoring the request URL. + It will also start a UDP server on the indicated UDP port that responds to the following commands: - "hostname": Returns the server's hostname