From 6e52e2ea739f4bd0f6ac4e23be0836615a5d7808 Mon Sep 17 00:00:00 2001 From: devploit Date: Mon, 29 May 2023 01:50:30 +0200 Subject: [PATCH] Fix some typos, add nobanner option and improve visualization --- .goreleaser.yaml | 2 +- README.md | 44 ++++++++++++++- cmd/api.go | 29 +++++++--- cmd/requester.go | 140 ++++++++++++++++++++++++++++++++++------------- cmd/root.go | 60 +++++++++++--------- go.mod | 1 - 6 files changed, 198 insertions(+), 78 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 0b06b81..57bdcc6 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -13,7 +13,7 @@ builds: - -s -w -X main.Version={{.Version}} -X main.BuildDate={{.CommitDate}} ignore: - goos: darwin - goarch: 386 + goarch: i386 - goos: windows goarch: arm64 diff --git a/README.md b/README.md index b2b82de..9c50a4e 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,15 @@ Usage: dontgo403 [flags] Flags: - -b, --bypassIp string Try bypass tests with a specific IP address (or hostname). i.e.: 'X-Forwarded-For: 192.168.0.1' instead of 'X-Forwarded-For: 127.0.0.1' - -d, --delay int Set a delay (in ms) between each request. Default: 0ms + -i, --bypassIp string Try bypass tests with a specific IP address (or hostname). i.e.: 'X-Forwarded-For: 192.168.0.1' instead of 'X-Forwarded-For: 127.0.0.1' + -d, --delay int Set a delay (in ms) between each request (default 0ms) -f, --folder string Define payloads folder (if it's not in the same path as binary) -H, --header strings Add a custom header to the requests (can be specified multiple times) -h, --help help for dontgo403 --http Set HTTP schema for request-file requests (default HTTPS) -t, --httpMethod string HTTP method to use (default 'GET') - -m, --max_goroutines int Set the max number of goroutines working at same time. Default: 50 (default 50) + -m, --max_goroutines int Set the max number of goroutines working at same time (default 50) + --nobanner Set nobanner ON (default OFF) -p, --proxy string Proxy URL. For example: http://127.0.0.1:8080 -r, --request-file string Path to request file to load flags from -u, --uri string Target URL @@ -46,6 +47,43 @@ Flags: ### Usage +Output example: +```bash + .#%%: -#%%%*. +#%%#+. + =@*#@: =@+ .%%.:+- =@* + :::. ... .#@= *@: *@: *@: :##%@- + :::. :::. .. -:..-. .. :: =%%%%@@%:=@*. :%% =*- :@% + .::::::::::. .::::::. .:::::::::. ::::::::. .:::::::::. .=::..:==-=+++:. +#. -*#%#+. =*###+. +.:::....::::. .:::....:::. .::::...:::. ..::::.. ::::....::: --::..-=+*=:. +:::. :::. :::. .::: .::: .::: :::. :::: .::: -=::-*#+=: +:::: .:::. :::: :::: .::: .::: :::. .:::. :::. +-:::=::. + :::::.:::::. ::::..:::: .::: .::: .:::.:. .:::::::::. .+=:::::. + ..::::.::: ..::::.. .::: ::: ..:::. .....::::. -=:.:: + .::: :::: + :::::..::::. + +Target: https://domain.com/admin +Headers: false +User Agent: dontgo403 +Proxy: false +Method: GET +Payloads folder: payloads +Custom bypass IP: false +Verbose: false +━━━━━━━━━━━━━ DEFAULT REQUEST ━━━━━━━━━━━━━ +403 429 bytes https://domain.com/admin + +━━━━━━━━━━━━━ VERB TAMPERING ━━━━━━━━━━━━━━ + +━━━━━━━━━━━━━ HEADERS ━━━━━━━━━━━━━━━━━━━━━ + +━━━━━━━━━━━━━ CUSTOM PATHS ━━━━━━━━━━━━━━━━ +200 2047 bytes https://domain.com/;///..admin + +━━━━━━━━━━━━━ CASE SWITCHING ━━━━━━━━━━━━━━ +200 2047 bytes https://domain.com/%61dmin +``` + Basic usage: ```bash ./dontgo403 -u https://domain.com/admin diff --git a/cmd/api.go b/cmd/api.go index 6638db5..adc4477 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -3,6 +3,7 @@ package cmd import ( "bufio" "crypto/tls" + "io" "io/ioutil" "log" "net" @@ -20,9 +21,14 @@ func parseFile(filename string) ([]string, error) { if err != nil { return nil, err } - defer file.Close() + defer func(file *os.File) { + err := file.Close() + if err != nil { + log.Fatalf("{#err}") + } + }(file) - lines := []string{} + var lines []string scanner := bufio.NewScanner(file) for scanner.Scan() { lines = append(lines, scanner.Text()) @@ -83,7 +89,12 @@ func request(method, uri string, headers []header, proxy *url.URL) (int, []byte, if err != nil { return 0, nil, err } - defer res.Body.Close() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Fatalf("{#err}") + } + }(res.Body) resp, err := httputil.DumpResponse(res, true) if err != nil { @@ -100,10 +111,10 @@ func loadFlagsFromRequestFile(requestFile string, schema bool, verbose bool) { if err != nil { log.Fatalf("Error reading request file: %v", err) } - http_schema := "https://" + httpSchema := "https://" if schema != false { - http_schema = "http://" + httpSchema = "http://" } // Split the request into lines @@ -114,16 +125,16 @@ func loadFlagsFromRequestFile(requestFile string, schema bool, verbose bool) { // Extract the HTTP method and URL from the first line of the request parts := strings.Split(firstLine, " ") - uri := http_schema + host[1] + parts[1] + uri := httpSchema + host[1] + parts[1] // Extract headers from the request and assign them to the req_headers slice - req_headers := []string{} + var reqHeaders []string for _, h := range headers { if len(h) > 0 { - req_headers = append(req_headers, h) + reqHeaders = append(reqHeaders, h) } } // Assign the extracted values to the corresponding flag variables - requester(uri, proxy, useragent, req_headers, bypassIp, folder, httpMethod, verbose) + requester(uri, proxy, useragent, reqHeaders, bypassIp, folder, httpMethod, verbose, nobanner) } diff --git a/cmd/requester.go b/cmd/requester.go index 1c195aa..3a6af64 100644 --- a/cmd/requester.go +++ b/cmd/requester.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "log" "net/url" "strconv" @@ -9,7 +10,6 @@ import ( "time" "unicode" - "github.com/cheynewallace/tabby" "github.com/fatih/color" "github.com/zenthangplus/goccm" ) @@ -18,11 +18,11 @@ type Result struct { line string statusCode int contentLength int + defaultReq bool } var _verbose bool -var default_sc int -var default_cl int +var defaultSc int var printMutex = &sync.Mutex{} // printResponse prints the results of HTTP requests in a tabular format with colored output based on the status codes. @@ -30,7 +30,7 @@ func printResponse(result Result) { printMutex.Lock() defer printMutex.Unlock() - t := tabby.New() + resultContentLength := strconv.Itoa(result.contentLength) + " bytes" var code string switch result.statusCode { @@ -44,39 +44,78 @@ func printResponse(result Result) { code = color.MagentaString(strconv.Itoa(result.statusCode)) } if _verbose != true { - if default_sc == result.statusCode || result.contentLength == 0 || result.statusCode == 404 { + if (defaultSc == result.statusCode || result.contentLength == 0 || result.statusCode == 404 || result.statusCode == 400) && result.defaultReq != true { return } else { - t.AddLine(code, color.BlueString(strconv.Itoa(result.contentLength)+" bytes"), result.line) + fmt.Printf("%s \t%20s %s\n", code, color.BlueString(resultContentLength), result.line) } } else { - t.AddLine(code, color.BlueString(strconv.Itoa(result.contentLength)+" bytes"), result.line) + fmt.Printf("%s \t%20s %s\n", code, color.BlueString(resultContentLength), result.line) } - t.Print() +} + +func showInfo(uri string, headers []string, userAgent string, proxy *url.URL, method string, folder string, bypassIp string, verbose bool, nobanner bool) { + if nobanner != true { + fmt.Println(` + .#%%: -#%%%*. +#%%#+. + =@*#@: =@+ .%%.:+- =@* + :::. ... .#@= *@: *@: *@: :##%@- + :::. :::. .. -:..-. .. :: =%%%%@@%:=@*. :%% =*- :@% + .::::::::::. .::::::. .:::::::::. ::::::::. .:::::::::. .=::..:==-=+++:. +#. -*#%#+. =*###+. +.:::....::::. .:::....:::. .::::...:::. ..::::.. ::::....::: --::..-=+*=:. +:::. :::. :::. .::: .::: .::: :::. :::: .::: -=::-*#+=: +:::: .:::. :::: :::: .::: .::: :::. .:::. :::. +-:::=::. + :::::.:::::. ::::..:::: .::: .::: .:::.:. .:::::::::. .+=:::::. + ..::::.::: ..::::.. .::: ::: ..:::. .....::::. -=:.:: + .::: :::: + :::::..::::. + `) + } + fmt.Printf("%s \t\t%s\n", "Target:", uri) + if len(headers[0]) != 0 { + for _, header := range headers { + fmt.Printf("%s \t\t%s\n", "Headers:", header) + } + } else { + fmt.Printf("%s \t\t%s\n", "Headers:", "false") + } + if len(proxy.Host) != 0 { + fmt.Printf("%s \t\t\t%s\n", "Proxy:", proxy.Host) + } else { + fmt.Printf("%s \t\t\t%s\n", "Proxy:", "false") + } + fmt.Printf("%s \t\t%s\n", "User Agent:", userAgent) + fmt.Printf("%s \t\t%s\n", "Method:", method) + fmt.Printf("%s \t%s\n", "Payloads folder:", folder) + if len(bypassIp) != 0 { + fmt.Printf("%s \t%s\n", "Custom bypass IP:", bypassIp) + } else { + fmt.Printf("%s \t%s\n", "Custom bypass IP:", "false") + } + fmt.Printf("%s \t\t%t\n", "Verbose:", verbose) } // requestDefault makes HTTP request to check the default response func requestDefault(uri string, headers []header, proxy *url.URL, method string) { - color.Cyan("\n[####] DEFAULT REQUEST [####]") + color.Cyan("\n━━━━━━━━━━━━━ DEFAULT REQUEST ━━━━━━━━━━━━━") - results := []Result{} + var results []Result statusCode, response, err := request(method, uri, headers, proxy) if err != nil { log.Println(err) } - results = append(results, Result{method, statusCode, len(response)}) - printResponse(Result{uri, statusCode, len(response)}) + results = append(results, Result{method, statusCode, len(response), true}) + printResponse(Result{uri, statusCode, len(response), true}) for _, result := range results { - default_sc = result.statusCode - default_cl = result.contentLength + defaultSc = result.statusCode } } // requestMethods makes HTTP requests using a list of methods from a file and prints the results. func requestMethods(uri string, headers []header, proxy *url.URL, folder string) { - color.Cyan("\n[####] VERB TAMPERING [####]") + color.Cyan("\n━━━━━━━━━━━━━ VERB TAMPERING ━━━━━━━━━━━━━━") var lines []string lines, err := parseFile(folder + "/httpmethods") @@ -84,7 +123,7 @@ func requestMethods(uri string, headers []header, proxy *url.URL, folder string) log.Fatalf("Error reading /httpmethods file: %v", err) } - w := goccm.New(max_goroutines) + w := goccm.New(maxGoroutines) for _, line := range lines { time.Sleep(time.Duration(delay) * time.Millisecond) @@ -95,7 +134,7 @@ func requestMethods(uri string, headers []header, proxy *url.URL, folder string) log.Println(err) } - printResponse(Result{line, statusCode, len(response)}) + printResponse(Result{line, statusCode, len(response), false}) w.Done() }(line) } @@ -104,7 +143,7 @@ func requestMethods(uri string, headers []header, proxy *url.URL, folder string) // requestHeaders makes HTTP requests using a list of headers from a file and prints the results. It can also bypass IP address restrictions by specifying a bypass IP address. func requestHeaders(uri string, headers []header, proxy *url.URL, bypassIp string, folder string, method string) { - color.Cyan("\n[####] HEADERS [####]") + color.Cyan("\n━━━━━━━━━━━━━ HEADERS ━━━━━━━━━━━━━━━━━━━━━") var lines []string lines, err := parseFile(folder + "/headers") @@ -127,7 +166,7 @@ func requestHeaders(uri string, headers []header, proxy *url.URL, bypassIp strin log.Fatalf("Error reading /simpleheaders file: %v", err) } - w := goccm.New(max_goroutines) + w := goccm.New(maxGoroutines) for _, ip := range ips { for _, line := range lines { @@ -142,7 +181,7 @@ func requestHeaders(uri string, headers []header, proxy *url.URL, bypassIp strin log.Println(err) } - printResponse(Result{line, statusCode, len(response)}) + printResponse(Result{line, statusCode, len(response), false}) w.Done() }(line, ip) } @@ -160,7 +199,7 @@ func requestHeaders(uri string, headers []header, proxy *url.URL, bypassIp strin log.Println(err) } - printResponse(Result{line, statusCode, len(response)}) + printResponse(Result{line, statusCode, len(response), false}) w.Done() }(simpleheader) } @@ -169,7 +208,7 @@ func requestHeaders(uri string, headers []header, proxy *url.URL, bypassIp strin // requestEndPaths makes HTTP requests using a list of custom end paths from a file and prints the results. func requestEndPaths(uri string, headers []header, proxy *url.URL, folder string, method string) { - color.Cyan("\n[####] CUSTOM PATHS [####]") + color.Cyan("\n━━━━━━━━━━━━━ CUSTOM PATHS ━━━━━━━━━━━━━━━━") var lines []string lines, err := parseFile(folder + "/endpaths") @@ -177,7 +216,7 @@ func requestEndPaths(uri string, headers []header, proxy *url.URL, folder string log.Fatalf("Error reading custom paths file: %v", err) } - w := goccm.New(max_goroutines) + w := goccm.New(maxGoroutines) for _, line := range lines { time.Sleep(time.Duration(delay) * time.Millisecond) @@ -188,7 +227,7 @@ func requestEndPaths(uri string, headers []header, proxy *url.URL, folder string log.Println(err) } - printResponse(Result{uri + line, statusCode, len(response)}) + printResponse(Result{uri + line, statusCode, len(response), false}) w.Done() }(line) } @@ -196,7 +235,7 @@ func requestEndPaths(uri string, headers []header, proxy *url.URL, folder string w.WaitAllDone() } -// requestMidPaths makes HTTP requests using a list of custom mid paths from a file and prints the results. +// requestMidPaths makes HTTP requests using a list of custom mid-paths from a file and prints the results. func requestMidPaths(uri string, headers []header, proxy *url.URL, folder string, method string) { var lines []string lines, err := parseFile(folder + "/midpaths") @@ -218,7 +257,7 @@ func requestMidPaths(uri string, headers []header, proxy *url.URL, folder string baseuri := strings.ReplaceAll(uri, uripath, "") baseuri = baseuri[:len(baseuri)-1] - w := goccm.New(max_goroutines) + w := goccm.New(maxGoroutines) for _, line := range lines { time.Sleep(time.Duration(delay) * time.Millisecond) @@ -236,7 +275,7 @@ func requestMidPaths(uri string, headers []header, proxy *url.URL, folder string log.Println(err) } - printResponse(Result{fullpath, statusCode, len(response)}) + printResponse(Result{fullpath, statusCode, len(response), false}) w.Done() }(line) } @@ -244,9 +283,9 @@ func requestMidPaths(uri string, headers []header, proxy *url.URL, folder string } } -// requestCapital makes HTTP requests by capitalizing each letter in the last part of the URI and prints the results. -func requestCapital(uri string, headers []header, proxy *url.URL, method string) { - color.Cyan("\n[####] CAPITALIZATION [####]") +// requestCaseSwitching makes HTTP requests by capitalizing each letter in the last part of the URI and try to use URL encoded characters. +func requestCaseSwitching(uri string, headers []header, proxy *url.URL, method string) { + color.Cyan("\n━━━━━━━━━━━━━ CASE SWITCHING ━━━━━━━━━━━━━━") x := strings.Split(uri, "/") var uripath string @@ -259,7 +298,7 @@ func requestCapital(uri string, headers []header, proxy *url.URL, method string) baseuri := strings.ReplaceAll(uri, uripath, "") baseuri = baseuri[:len(baseuri)-1] - w := goccm.New(max_goroutines) + w := goccm.New(maxGoroutines) for _, z := range uripath { time.Sleep(time.Duration(delay) * time.Millisecond) @@ -285,7 +324,31 @@ func requestCapital(uri string, headers []header, proxy *url.URL, method string) log.Println(err) } - printResponse(Result{fullpath, statusCode, len(response)}) + printResponse(Result{fullpath, statusCode, len(response), false}) + w.Done() + }(z) + } + + for _, z := range uripath { + time.Sleep(time.Duration(delay) * time.Millisecond) + w.Wait() + go func(z rune) { + encodedChar := fmt.Sprintf("%%%X", z) // convert rune to its hexadecimal ASCII value + newpath := strings.Replace(uripath, string(z), encodedChar, 1) + + var fullpath string + if uri[len(uri)-1:] == "/" { + fullpath = baseuri + newpath + "/" + } else { + fullpath = baseuri + "/" + newpath + } + + statusCode, response, err := request(method, fullpath, headers, proxy) + if err != nil { + log.Println(err) + } + + printResponse(Result{fullpath, statusCode, len(response), false}) w.Done() }(z) } @@ -293,7 +356,7 @@ func requestCapital(uri string, headers []header, proxy *url.URL, method string) } // requester is the main function that runs all the tests. -func requester(uri string, proxy string, userAgent string, req_headers []string, bypassIp string, folder string, method string, verbose bool) { +func requester(uri string, proxy string, userAgent string, reqHeaders []string, bypassIp string, folder string, method string, verbose bool, banner bool) { // Set up proxy if provided. if len(proxy) != 0 { if !strings.Contains(proxy, "http") { @@ -322,20 +385,21 @@ func requester(uri string, proxy string, userAgent string, req_headers []string, } // Parse custom headers from CLI arguments and add them to the headers slice. - if len(req_headers[0]) != 0 { - for _, _header := range req_headers { - header_split := strings.Split(_header, ":") - headers = append(headers, header{header_split[0], header_split[1]}) + if len(reqHeaders[0]) != 0 { + for _, _header := range reqHeaders { + headerSplit := strings.Split(_header, ":") + headers = append(headers, header{headerSplit[0], headerSplit[1]}) } } _verbose = verbose // Call each function that will send HTTP requests with different variations of headers and URLs. + showInfo(uri, reqHeaders, userAgent, userProxy, method, folder, bypassIp, verbose, banner) requestDefault(uri, headers, userProxy, method) requestMethods(uri, headers, userProxy, folder) requestHeaders(uri, headers, userProxy, bypassIp, folder, method) requestEndPaths(uri, headers, userProxy, folder, method) requestMidPaths(uri, headers, userProxy, folder, method) - requestCapital(uri, headers, userProxy, method) + requestCaseSwitching(uri, headers, userProxy, method) } diff --git a/cmd/root.go b/cmd/root.go index c850be1..c744539 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,19 +12,20 @@ import ( ) var ( - cfgFile string - uri string - proxy string - useragent string - max_goroutines int - delay int - req_headers []string - bypassIp string - folder string - httpMethod string - requestFile string - schema bool - verbose bool + cfgFile string + uri string + proxy string + useragent string + maxGoroutines int + delay int + reqHeaders []string + bypassIp string + folder string + httpMethod string + requestFile string + schema bool + verbose bool + nobanner bool ) // rootCmd @@ -42,24 +43,27 @@ var rootCmd = &cobra.Command{ if (fi.Mode() & os.ModeCharDevice) == 0 { bytes, _ := ioutil.ReadAll(os.Stdin) content := string(bytes) - lines := strings.Split(string(content), "\n") - lastchar := (lines[len(lines)-1]) + lines := strings.Split(content, "\n") + lastchar := lines[len(lines)-1] for _, line := range lines { uri = line if uri == lastchar { break } - requester(uri, proxy, useragent, req_headers, bypassIp, folder, httpMethod, verbose) + requester(uri, proxy, useragent, reqHeaders, bypassIp, folder, httpMethod, verbose, nobanner) } } else { if len(requestFile) > 0 { loadFlagsFromRequestFile(requestFile, schema, verbose) } else { if len(uri) == 0 { - cmd.Help() + err := cmd.Help() + if err != nil { + log.Fatalf("Error printing help: %v", err) + } log.Fatal() } - requester(uri, proxy, useragent, req_headers, bypassIp, folder, httpMethod, verbose) + requester(uri, proxy, useragent, reqHeaders, bypassIp, folder, httpMethod, verbose, nobanner) } } }, @@ -74,17 +78,18 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVarP(&uri, "uri", "u", "", "Target URL") - rootCmd.PersistentFlags().StringVarP(&proxy, "proxy", "p", "", "Proxy URL. For example: http://127.0.0.1:8080") - rootCmd.PersistentFlags().StringVarP(&useragent, "useragent", "a", "", "Set the User-Agent string (default 'dontgo403')") - rootCmd.PersistentFlags().IntVarP(&max_goroutines, "max_goroutines", "m", 50, "Set the max number of goroutines working at same time") + rootCmd.PersistentFlags().StringVarP(&bypassIp, "bypassIp", "i", "", "Try bypass tests with a specific IP address (or hostname). i.e.: 'X-Forwarded-For: 192.168.0.1' instead of 'X-Forwarded-For: 127.0.0.1'") rootCmd.PersistentFlags().IntVarP(&delay, "delay", "d", 0, "Set a delay (in ms) between each request (default 0ms)") - rootCmd.PersistentFlags().StringSliceVarP(&req_headers, "header", "H", []string{""}, "Add a custom header to the requests (can be specified multiple times)") - rootCmd.PersistentFlags().StringVarP(&bypassIp, "bypassIp", "b", "", "Try bypass tests with a specific IP address (or hostname). i.e.: 'X-Forwarded-For: 192.168.0.1' instead of 'X-Forwarded-For: 127.0.0.1'") rootCmd.PersistentFlags().StringVarP(&folder, "folder", "f", "", "Define payloads folder (if it's not in the same path as binary)") + rootCmd.PersistentFlags().StringSliceVarP(&reqHeaders, "header", "H", []string{""}, "Add a custom header to the requests (can be specified multiple times)") + rootCmd.PersistentFlags().BoolVarP(&schema, "http", "", false, "Set HTTP schema for request-file requests (default HTTPS)") rootCmd.PersistentFlags().StringVarP(&httpMethod, "httpMethod", "t", "", "HTTP method to use (default 'GET')") + rootCmd.PersistentFlags().IntVarP(&maxGoroutines, "max_goroutines", "m", 50, "Set the max number of goroutines working at same time") + rootCmd.PersistentFlags().BoolVarP(&nobanner, "nobanner", "", false, "Set nobanner ON (default OFF)") + rootCmd.PersistentFlags().StringVarP(&proxy, "proxy", "p", "", "Proxy URL. For example: http://127.0.0.1:8080") rootCmd.PersistentFlags().StringVarP(&requestFile, "request-file", "r", "", "Path to request file to load flags from") - rootCmd.PersistentFlags().BoolVarP(&schema, "http", "", false, "Set HTTP schema for request-file requests (default HTTPS)") + rootCmd.PersistentFlags().StringVarP(&uri, "uri", "u", "", "Target URL") + rootCmd.PersistentFlags().StringVarP(&useragent, "useragent", "a", "", "Set the User-Agent string (default 'dontgo403')") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Set verbose mode ON (default OFF)") } @@ -108,6 +113,9 @@ func initConfig() { // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + _, err := fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + if err != nil { + log.Fatalf("{#err}") + } } } diff --git a/go.mod b/go.mod index 76174e6..9d5b524 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module dontgo403 go 1.19 require ( - github.com/cheynewallace/tabby v1.1.1 github.com/fatih/color v1.13.0 github.com/spf13/cobra v1.5.0 github.com/spf13/viper v1.13.0