Merge pull request #92850 from tallclair/netexec

Enhance agnhost netexec for SSRF E2Es
This commit is contained in:
Kubernetes Prow Robot 2020-11-02 18:10:14 -08:00 committed by GitHub
commit 17376e6aef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 83 additions and 25 deletions

View File

@ -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`. - `protocol`: The protocol which will be used when making the request. Default value: `http`.
Acceptable values: `http`, `udp`, `sctp`. Acceptable values: `http`, `udp`, `sctp`.
- `tries`: The number of times the request will be performed. Default value: `1`. - `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 - `/exit`: Closes the server with the given code and graceful shutdown. The endpoint's parameters
are: are:
- `code`: The exit code for the process. Default value: 0. Allows an integer [0-127]. - `code`: The exit code for the process. Default value: 0. Allows an integer [0-127].
@ -407,6 +407,8 @@ Starts a HTTP(S) server on given port with the following endpoints:
it exited. it exited.
- `/hostname`: Returns the server's hostname. - `/hostname`: Returns the server's hostname.
- `/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 - `/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 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. error message). Returns `200 OK` if the command succeeded, `417 Expectation Failed` if not.
@ -419,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 will be upgraded to HTTPS. The image has default, `localhost`-based cert/privkey files at
`/localhost.crt` and `/localhost.key` (see: [`porter` subcommand](#porter)) `/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: It will also start a UDP server on the indicated UDP port that responds to the following commands:
- `hostname`: Returns the server's hostname - `hostname`: Returns the server's hostname

View File

@ -40,13 +40,14 @@ import (
) )
var ( var (
httpPort = 8080 httpPort = 8080
udpPort = 8081 udpPort = 8081
sctpPort = -1 sctpPort = -1
shellPath = "/bin/sh" shellPath = "/bin/sh"
serverReady = &atomicBool{0} serverReady = &atomicBool{0}
certFile = "" certFile = ""
privKeyFile = "" privKeyFile = ""
httpOverride = ""
) )
// CmdNetexec is used by agnhost Cobra. // CmdNetexec is used by agnhost Cobra.
@ -69,7 +70,7 @@ var CmdNetexec = &cobra.Command{
- "protocol": The protocol which will be used when making the request. Default value: "http". - "protocol": The protocol which will be used when making the request. Default value: "http".
Acceptable values: "http", "udp", "sctp". Acceptable values: "http", "udp", "sctp".
- "tries": The number of times the request will be performed. Default value: "1". - "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 - "/exit": Closes the server with the given code and graceful shutdown. The endpoint's parameters
are: are:
- "code": The exit code for the process. Default value: 0. Allows an integer [0-127]. - "code": The exit code for the process. Default value: 0. Allows an integer [0-127].
@ -83,6 +84,8 @@ var CmdNetexec = &cobra.Command{
it exited. it exited.
- "/hostname": Returns the server's hostname. - "/hostname": Returns the server's hostname.
- "/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 - "/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 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. error message). Returns "200 OK" if the command succeeded, "417 Expectation Failed" if not.
@ -91,6 +94,13 @@ var CmdNetexec = &cobra.Command{
Returns a JSON with the fields "output" (containing the file's name on the server) and Returns a JSON with the fields "output" (containing the file's name on the server) and
"error" containing any potential server side errors. "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: It will also start a UDP server on the indicated UDP port that responds to the following commands:
- "hostname": Returns the server's hostname - "hostname": Returns the server's hostname
@ -112,6 +122,7 @@ func init() {
"File containing an x509 private key matching --tls-cert-file") "File containing an x509 private key matching --tls-cert-file")
CmdNetexec.Flags().IntVar(&udpPort, "udp-port", 8081, "UDP Listen Port") CmdNetexec.Flags().IntVar(&udpPort, "udp-port", 8081, "UDP Listen Port")
CmdNetexec.Flags().IntVar(&sctpPort, "sctp-port", -1, "SCTP 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. // atomicBool uses load/store operations on an int32 to simulate an atomic boolean.
@ -135,7 +146,21 @@ func (a *atomicBool) get() bool {
func main(cmd *cobra.Command, args []string) { func main(cmd *cobra.Command, args []string) {
exitCh := make(chan shutdownRequest) 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) go startUDPServer(udpPort)
if sctpPort != -1 { if sctpPort != -1 {
@ -150,22 +175,24 @@ func main(cmd *cobra.Command, args []string) {
} }
} }
func addRoutes(exitCh chan shutdownRequest) { func addRoutes(mux *http.ServeMux, exitCh chan shutdownRequest) {
http.HandleFunc("/", rootHandler) mux.HandleFunc("/", rootHandler)
http.HandleFunc("/clientip", clientIPHandler) mux.HandleFunc("/clientip", clientIPHandler)
http.HandleFunc("/echo", echoHandler) mux.HandleFunc("/dial", dialHandler)
http.HandleFunc("/exit", func(w http.ResponseWriter, req *http.Request) { exitHandler(w, req, exitCh) }) mux.HandleFunc("/echo", echoHandler)
http.HandleFunc("/hostname", hostnameHandler) mux.HandleFunc("/exit", func(w http.ResponseWriter, req *http.Request) { exitHandler(w, req, exitCh) })
http.HandleFunc("/shell", shellHandler) mux.HandleFunc("/healthz", healthzHandler)
http.HandleFunc("/upload", uploadHandler) mux.HandleFunc("/hostname", hostnameHandler)
http.HandleFunc("/dial", dialHandler) mux.HandleFunc("/redirect", redirectHandler)
http.HandleFunc("/healthz", healthzHandler) mux.HandleFunc("/shell", shellHandler)
mux.HandleFunc("/upload", uploadHandler)
// older handlers // older handlers
http.HandleFunc("/hostName", hostNameHandler) mux.HandleFunc("/hostName", hostNameHandler)
http.HandleFunc("/shutdown", shutdownHandler) mux.HandleFunc("/shutdown", shutdownHandler)
} }
func startServer(server *http.Server, exitCh chan shutdownRequest, fn func() error) { func startServer(server *http.Server, exitCh chan shutdownRequest, fn func() error) {
log.Printf("Started HTTP server on port %d", httpPort)
go func() { go func() {
re := <-exitCh re := <-exitCh
ctx, cancelFn := context.WithTimeout(context.Background(), re.timeout) ctx, cancelFn := context.WithTimeout(context.Background(), re.timeout)
@ -190,8 +217,18 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
} }
func echoHandler(w http.ResponseWriter, r *http.Request) { func echoHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("GET /echo?msg=%s", r.FormValue("msg")) msg := r.FormValue("msg")
fmt.Fprintf(w, "%s", 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) { func clientIPHandler(w http.ResponseWriter, r *http.Request) {
@ -488,6 +525,22 @@ func hostNameHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, getHostName()) 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. // udp server supports the hostName, echo and clientIP commands.
func startUDPServer(udpPort int) { func startUDPServer(udpPort int) {
serverAddress, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", udpPort)) serverAddress, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", udpPort))
@ -497,7 +550,7 @@ func startUDPServer(udpPort int) {
defer serverConn.Close() defer serverConn.Close()
buf := make([]byte, 2048) buf := make([]byte, 2048)
log.Printf("Started UDP server") log.Printf("Started UDP server on port %d", udpPort)
// Start responding to readiness probes. // Start responding to readiness probes.
serverReady.set(true) serverReady.set(true)
defer func() { defer func() {