diff --git a/README.md b/README.md index fd6705c..fbe063e 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,10 @@ $ turtle -h $ turtle slowloris -h ``` +To learn more, please checkout one of the following guides: + +- [Is my server Slowloris-proofed?](/docs/usage/cli-slowloris.md) + ### Turtle Golang Library For the Golang library, documentation can be found on [GoDoc][godoc]. diff --git a/cmd/proof/main.go b/cmd/turtle-proof/main.go similarity index 73% rename from cmd/proof/main.go rename to cmd/turtle-proof/main.go index c8d9dd5..3d3c18b 100644 --- a/cmd/proof/main.go +++ b/cmd/turtle-proof/main.go @@ -3,8 +3,8 @@ package main import ( "context" "errors" - "fmt" "io" + "log/slog" "net" "net/http" "os" @@ -14,6 +14,8 @@ import ( "github.com/alecthomas/kong" ) +var logger = slog.Default().WithGroup("turtle-proof") + type CLI struct { ServerAddr string `cmd:"server-addr" help:"the address to listen on" default:"127.0.0.1:8889"` Scenario string `cmd:"scenario" enum:"none,proof" help:"the scenario to run" default:"none"` @@ -58,16 +60,20 @@ func (c *CLI) CreateServer() *http.Server { WriteTimeout: c.ServerWriteTimeout, ConnState: func(conn net.Conn, state http.ConnState) { - fmt.Println("ConnState", conn.RemoteAddr(), state) + logger.Info( + "ConnState", + slog.String("remote_addr", conn.RemoteAddr().String()), + slog.String("state", state.String()), + ) }, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Println("here") + logger.Info("handling request") if r.Body != nil { - fmt.Println("read body") + logger.Info("reading body") if _, err := io.ReadAll(r.Body); err != nil { - fmt.Println("read error", err) + logger.Error("read error", slog.String("error", err.Error())) w.WriteHeader(http.StatusInternalServerError) } } @@ -78,9 +84,29 @@ func (c *CLI) CreateServer() *http.Server { } } +const description = ` +Proof HTTP server for turtle test. + +## Start with default settings + + turtle-proof + +## Start with proofed settings + + turtle-proof --scenario proof + +## Using custom settings + + turtle-proof --server-addr="127.0.0.1:8888" --server-read-header-timeout 3s --server-read-timeout 60s --server-write-timeout 60s +` + func main() { cli := &CLI{} - cliCtx := kong.Parse(cli) + cliCtx := kong.Parse( + cli, + kong.Name("turtle-proof"), + kong.Description(description), + ) if err := cli.defaults(); err != nil { cliCtx.FatalIfErrorf(err) @@ -97,6 +123,9 @@ func main() { } }() + time.Sleep(300 * time.Millisecond) // wait for server start + logger.Info("server started", slog.String("addr", server.Addr)) + <-ctx.Done() shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) diff --git a/docs/demo/slowloris-invulnerable.cast b/docs/demo/slowloris-invulnerable.cast new file mode 100644 index 0000000..b9fe426 --- /dev/null +++ b/docs/demo/slowloris-invulnerable.cast @@ -0,0 +1,12 @@ +Set Shell zsh +Set FontSize 11 +Escape +Type "[200~turtle slowloris http://localhost:8889 --http-send-gibberish" +Escape +Type "[201~" +Enter +Sleep 10s +Type "q" +Sleep 1s +Ctrl+D +Output slowloris-invulnerable.gif diff --git a/docs/demo/slowloris-invulnerable.gif b/docs/demo/slowloris-invulnerable.gif new file mode 100644 index 0000000..7132f04 Binary files /dev/null and b/docs/demo/slowloris-invulnerable.gif differ diff --git a/docs/demo/slowloris-vulnerable.cast b/docs/demo/slowloris-vulnerable.cast new file mode 100644 index 0000000..1bb6301 --- /dev/null +++ b/docs/demo/slowloris-vulnerable.cast @@ -0,0 +1,12 @@ +Set Shell zsh +Set FontSize 11 +Escape +Type "[200~turtle slowloris http://localhost:8889 --http-send-gibberish" +Escape +Type "[201~" +Enter +Sleep 10s +Type "q" +Sleep 1s +Ctrl+D +Output slowloris-vulnerable.gif diff --git a/docs/demo/slowloris-vulnerable.gif b/docs/demo/slowloris-vulnerable.gif new file mode 100644 index 0000000..ce771bd Binary files /dev/null and b/docs/demo/slowloris-vulnerable.gif differ diff --git a/docs/usage/cli-slowloris.md b/docs/usage/cli-slowloris.md new file mode 100644 index 0000000..8dbb640 --- /dev/null +++ b/docs/usage/cli-slowloris.md @@ -0,0 +1,62 @@ +# Is my server Slowloris-proofed? + +[Slowloris attack][cf_slowloris] attempts to break an HTTP server by sending partial HTTP request, +which contains never finish HTTP header lines: + +``` +GET / HTTP 1.1 # this is the only line required to start an HTTP request +HOST example.com +User-Agent my-user-agent +Header-Name Header-Value +# ... keep sending gibberish header name & value lines +``` + +Since the HTTP request is never ended, vulnerable server keeps the connection open. As a result, server side resources like memory, file descriptor will be consumed. + +Invulnerable server should close the connection after a specified time if the request is unable to read completely. + +We can use turtle to validate if an HTTP endpoint is Slowloris-proofed: + +- if the server is immune to the attack, we should see closed events, and the total number of requests should be more than the test connections; +- if the server is vulnerable to the attack, the connections will be kept opened until test finished. + +## Validating via CLI + +> **NOTE** We can start a test server with `turtle-proof`. For setup guide, please see [turtle proof server][turtle-proof-server]. + +1. Start a vulnerable server: + +``` +$ turtle-proof +2023/09/04 11:33:04 INFO server started turtle-proof.addr=127.0.0.1:8889 +``` + +2. Launch the test with the `slowloris` sub-command: + +``` +$ turtle slowloris http://127.0.0.1:8889 --http-send-gibberish +``` + +We should see output similar to below, where the number of connections stays at 100 without closing. + +![](/docs/demo/slowloris-vulnerable.gif) + +3. Start a invulnerable server: + +``` +$ turtle-proof --scenario=proof +2023/09/04 11:36:49 INFO server started turtle-proof.addr=127.0.0.1:8889 +``` + +4. Launch the test again + +``` +$ turtle slowloris http://127.0.0.1:8889 --http-send-gibberish +``` + +Now, we should see many closing / reopening events like this: + +![](/docs/demo/slowloris-invulnerable.gif) + +[cf_slowloris]: https://www.cloudflare.com/learning/ddos/ddos-attack-tools/slowloris/ +[turtle-proof-server]: ./turtle-proof-server.md \ No newline at end of file